Intro

The purpose of this analysis is to find synergy biomarkers using the emba R package in the cascade boolean model datasets (the cascade is the name of the topology used). Particularly, we will investigate \(4\) synergies: AK-PD, PD-PI, PD-G2, PI-D1 and try to find causal mechanisms that might explain why and where (pathways) these synergies manifest.

Note that AK is an AKT inhibitor and PI is a PI3K inhibitor and they both target the PI3K/AKT/mTOR pathway. The combination of such an inhibitor with a MEK inhibitor - PD - that targets the MAPK/ERK pathway, has proven to be more effective than the single drug treatment during clinical trials with patients that had advanced colorectal carcinoma (source). The G2 drug is a PDPK1 inhibitor and the D1 drug is an inhibitor of RSK isoforms ( RPS6KA1, RPS6KA3, RPS6KA2, RPS6KA6)

The boolean model datasets are in total \(9\): one for each cell line of interest (8 cell lines) where the models were fitted to a specific steady state in each case and one for the so-called random models which were generated randomly in the sense that were fitted only to a proliferation state (simulations were done using the DrugLogics software modules Gitsbe and Drabme).

Input

Loading libraries:

library(emba)
library(usefun)
library(ComplexHeatmap)
library(circlize)
library(dplyr)
library(tibble)
library(DT)
library(ggpubr)
library(Ckmeans.1d.dp)

We load the cell-specific input data:

# Cell Lines
cell.lines = c("A498", "AGS", "DU145", "colo205", "SW620", "SF295", "UACC62", "MDA-MB-468")

cell.line.dirs = sapply(cell.lines, function(cell.line) {
  paste0(getwd(), "/", cell.line)
})

# Model predictions
model.predictions.files = sapply(cell.line.dirs, function(cell.line.dir) {
  paste0(cell.line.dir, "/model_predictions")
})

model.predictions.per.cell.line = lapply(model.predictions.files, function(file) {   
  get_model_predictions(file) 
})

# Observed synergies
observed.synergies.files = sapply(cell.line.dirs, function(cell.line.dir) {
  paste0(cell.line.dir, "/observed_synergies")
})

observed.synergies.per.cell.line = lapply(observed.synergies.files, function(file) {
  get_observed_synergies(file)
})

# Models Stable State (1 per model)
models.stable.state.files = sapply(cell.line.dirs, function(cell.line.dir) {
  paste0(cell.line.dir, "/models_stable_state")
})

models.stable.state.per.cell.line = lapply(models.stable.state.files, function(file) { 
  as.matrix(read.table(file, check.names = FALSE))
})

# Models Link Operators
models.link.operator.files = sapply(cell.line.dirs, function(cell.line.dir) {
  paste0(cell.line.dir, "/models_link_operator")
})

models.link.operators.per.cell.line = lapply(models.link.operator.files, function(file) {
  as.matrix(read.table(file, check.names = FALSE))
})

The random model input data:

random.dir = paste0(getwd(), "/random")
random.model.predictions = get_model_predictions(paste0(random.dir, "/model_predictions"))

random.models.stable.state = as.matrix(
  read.table(file = paste0(random.dir, "/models_stable_state"), check.names = FALSE)
)

random.models.link.operator =
  as.matrix(read.table(file = paste0(random.dir, "/models_link_operator"), check.names = FALSE))

# the node names used in our analysis
node.names = colnames(random.models.stable.state)
# the tested drug comtions
drug.combos = colnames(random.model.predictions)

Synergy Biomarker Analysis

Using the generic function biomarker_synergy_analysis from the emba R package, we can find synergy biomarkers i.e. nodes whose activity and boolean equation parameterization (link operator) affect the manifestation of synergies in the boolean models. Models are classified based on whether they predict or not each of the predicted synergies found in each boolean dataset.

First we run the analysis on the cell-specific boolean model datasets (note that every input to the biomarker_synergy_analysis function changes per cell line):

cell.specific.synergy.analysis.res = list()

for (cell.line in cell.lines) {
  cell.specific.synergy.analysis.res[[cell.line]] =
    biomarker_synergy_analysis(model.predictions.per.cell.line[[cell.line]],
      models.stable.state.per.cell.line[[cell.line]], 
      models.link.operators.per.cell.line[[cell.line]],
      observed.synergies.per.cell.line[[cell.line]], threshold = 0.7)
}

Next we run the analysis on the random boolean model datasets (note that the only input to the biomarker_synergy_analysis function that changes is the observed synergies per cell line - the rest is the same data from the random model dataset):

# Synergy Biomarkers for cell proliferation models
random.synergy.analysis.res = list()

for (cell.line in cell.lines) {
  random.synergy.analysis.res[[cell.line]] =
    biomarker_synergy_analysis(random.model.predictions, 
      random.models.stable.state, random.models.link.operator, 
      observed.synergies.per.cell.line[[cell.line]], threshold = 0.7)
}

Observed synergies

Each of the cell lines studied has a different set of observed synergies (drug comtions that were found synergistic across all the 153 tested ones). In this section, we will visualize the cell lines’ observed synergies and mark the synergies that were also predicted by the cell-specific models and the random-generated ones. First, we get the biomarkers for these synergies from each cell line:

total.predicted.synergies.cell.specific =
  unique(unlist(sapply(cell.specific.synergy.analysis.res, function(x) { x$predicted.synergies})))
total.predicted.synergies.cell.specific.num = length(total.predicted.synergies.cell.specific)

The same for the random models:

total.predicted.synergies.random = 
  unique(unlist(sapply(random.synergy.analysis.res, function(x) { x$predicted.synergies})))
total.predicted.synergies.random.num = length(total.predicted.synergies.random)

Then, we get the observed synergies from each cell line in a data.frame:

observed.synergies.res = get_observed_synergies_per_cell_line(cell.line.dirs, drug.combos)

# remove drug comtions which are not observed in any of the cell lines
observed.synergies.res = prune_columns_from_df(observed.synergies.res, value = 0)

total.observed.synergies = colnames(observed.synergies.res)
total.observed.synergies.num = length(total.observed.synergies)

Lastly, we visualize the observed and predicted synergies for all cell lines in one heatmap:

# color the cell-specific predicted synergies
predicted.synergies.colors = rep("black", total.observed.synergies.num)
names(predicted.synergies.colors) = total.observed.synergies
common.predicted.synergies = intersect(total.predicted.synergies.cell.specific,
                                       total.predicted.synergies.random)
cell.specific.only.predicted.synergies = 
  total.predicted.synergies.cell.specific[!total.predicted.synergies.cell.specific %in% total.predicted.synergies.random]
random.only.predicted.synergies = 
  total.predicted.synergies.random[!total.predicted.synergies.random %in% total.predicted.synergies.cell.specific]

predicted.synergies.colors[total.observed.synergies %in% 
                           common.predicted.synergies] = "blue"
predicted.synergies.colors[total.observed.synergies %in% 
                           cell.specific.only.predicted.synergies] = "orange"
predicted.synergies.colors[total.observed.synergies %in% 
                           random.only.predicted.synergies] = "purple"

# define a coloring
obs.synergies.col.fun = colorRamp2(c(0, 1), c("red", "green"))

observed.synergies.heatmap = 
  Heatmap(matrix = as.matrix(observed.synergies.res), 
          col = obs.synergies.col.fun,
          column_title = "Observed synergies per cell line",
          column_title_gp = gpar(fontsize = 20),
          column_names_gp = gpar(col = predicted.synergies.colors),
          row_title = "Cell Lines", row_title_side = "left",
          row_dend_side = "right", row_names_side = "left",
          rect_gp = gpar(col = "black", lwd = 0.3),
          heatmap_legend_param = list(at = c(1, 0), labels = c("YES", "NO"), 
            color_bar = "discrete", title = "Observed", direction = "vertical"))

lgd = Legend(at = c("Cell-specific", "Random", "Both"), title = "Predicted", 
             legend_gp = gpar(fill = c("orange", "purple", "blue")))

draw(observed.synergies.heatmap,  heatmap_legend_list = list(lgd), 
     heatmap_legend_side = "right")

  • The cell-specific models predicted 27 of the 40 observed synergies found across the 8 cell lines, whereas the random models predicted 27 of the them. Thus, the total true positive coverage for all the models across all cell lines is 72.5%
  • Note that there exist synergies which were observed in all cell lines (AK-BI, PI-D1)
  • AK-G4 and 5Z-D1 are observed synergies that only the cell-specific models could predict, whereas the G2-P5 and PI-D4 are observed synergies that only the random models could predict. This shows us that a complimentary approach is needed when searching for biomarkers as the two different kind of models (trained to a specific activity state profile vs trained to proliferation) although they share common true positives regarding the synergies they predict, there are also synergies only a specific class of models could predict.
  • The \(3\) synergies of interest, AK-PD, PD-PI and PD-G2 were observed in the A498 cell line and predicted by both the cell-specific and random models.

AK-PD biomarkers

As observed above, the AK-PD synergy was predicted by both the cell specific and random models in the A498 cell line.

Cell-specific (A498)

We get the average state and link operator differences per network node for the A498 cell line from the cell-specific models:

AK.PD.avg.state.diff.cell.specific = cell.specific.synergy.analysis.res$A498$diff.state.synergies.mat["AK-PD", ]
AK.PD.avg.link.diff.cell.specific  = cell.specific.synergy.analysis.res$A498$diff.link.synergies.mat["AK-PD", ]

We build the network from the topology file:

topology.file = paste0(getwd(), "/topology")
net = construct_network(topology.file = topology.file, models.dir =  paste0(getwd(), "/AGS/models"))

# a static layout for plotting the same network always
coordinates.file = paste0(getwd(), "/network_xy_coordinates")
nice.layout = as.matrix(read.table(coordinates.file))

Activity state biomarkers

We will now visualize the nodes average state differences in a network graph. Note that the good models are those that predicted the AK-PD drug comtion to be synergistic and were contrasted to those that predicted it to be antagonistic (bad models). The number of models in each category were:

model.predictions = model.predictions.per.cell.line[["A498"]]
models.stable.state = models.stable.state.per.cell.line[["A498"]]
drug.comb = "AK-PD"

good.models.num = sum(model.predictions[, drug.comb] == 1 & !is.na(model.predictions[, drug.comb]))
# unique good models
# models.link.operator = models.link.operators.per.cell.line$A498
# nrow(unique(models.link.operator[model.predictions[, drug.comb] == 1 & !is.na(model.predictions[, drug.comb]), ]))
bad.models.num  = sum(model.predictions[, drug.comb] == 0 & !is.na(model.predictions[, drug.comb]))

pretty_print_string(paste0("Number of 'good' models (AK-PD synergistic) in the A498 cell line: ", good.models.num))

Number of ‘good’ models (AK-PD synergistic) in the A498 cell line: 26

pretty_print_string(paste0("Number of 'bad' models (AK-PD antagonistic) in the A498 cell line: ", bad.models.num))

Number of ‘bad’ models (AK-PD antagonistic) in the A498 cell line: 2672

plot_avg_state_diff_graph(net, diff = AK.PD.avg.state.diff.cell.specific, 
  layout = nice.layout, title = "AK-PD activity state biomarkers (Cell specific models - A498)")

Thus, we can identify the active state biomarkers:

AK.PD.active.biomarkers = AK.PD.avg.state.diff.cell.specific[AK.PD.avg.state.diff.cell.specific > 0.7]
pretty_print_vector_names(AK.PD.active.biomarkers)

1 node: ERK_f

So, the AK-PD synergy manifests in cancer cell models that have the ERK_f family logical node in an active state. The MAPK-ERK signaling pathway has been studied a lot and has been found to be overexpressed/have increased activity in cancers and as such cancer treatments that include the inhibition of that pathway are found to be most beneficial.

Paper evidence for ERK overexpression in cancer (there are many):

The inhibited state biomarkers are:

AK.PD.inhibited.biomarkers = AK.PD.avg.state.diff.cell.specific[AK.PD.avg.state.diff.cell.specific < -0.7]
pretty_print_vector_names(AK.PD.inhibited.biomarkers)

2 nodes: PTPN11, GAB_f

We will demonstrate that the PTPN11 and GAB_f inhibited biomarkers are a direct consequence of the overepression of ERK_f:


If we check the logical equations related to the above biomarkers we see that:

pretty_print_string("ERK_f *=  (  MEK_f ) AND/OR NOT  (  ( DUSP6 )  or PPP1CA )")

ERK_f *= ( MEK_f ) AND/OR NOT ( ( DUSP6 ) or PPP1CA )

pretty_print_string("GAB_f *=  (  GRB2 ) AND/OR NOT ( ERK_f )")

GAB_f *= ( GRB2 ) AND/OR NOT ( ERK_f )

pretty_print_string("PTPN11 *=  (  GAB_f )")

PTPN11 *= ( GAB_f )

So, pretty much if the GAB_f node is more inhibited in the models that predicted the AK-PD synergy (good models), then PTPN11 is also as well. Also the average activity difference of the GRB2 node is -0.3434189, which makes the GAB_f node more inhibited in the good models since it’s activity is mostly dependent on the ERK_f node, which is mostly overexpressed in the good models. All in all, the overexpression of ERK_f is what causes the two other inhibited biomarkers.


Synergy subsets analysis

It will be interesting to find all the possible synergy sets and subsets that include the AK-PD as the extra synergy. Using these synergy sets, models that predict a set of synergies will be contrasted to models that predicted the same set with the addition of the extra AK-PD synergy. Thus we could find synergy biomarkers that allow already good predicting models to predict the additional synergy of interest. This investigation will allow us thus to refine the activity state and link operator biomarkers we found above.

We first construct two matrices: in the first, each row is a set vs subset average activity difference vector of the network nodes, while on the second each row is a set vs subset average link operator difference vector of the network nodes:

model.predictions = model.predictions.per.cell.line$A498
models.stable.state = models.stable.state.per.cell.line$A498
models.link.operator = models.link.operators.per.cell.line$A498
  
res = get_synergy_comparison_sets(cell.specific.synergy.analysis.res$A498$synergy.subset.stats)
AK.PD.res = res %>% filter(synergies == "AK-PD")

diff.state.list = list()
diff.link.list = list()
for (i in 1:nrow(AK.PD.res)) {
  synergy.set    = AK.PD.res[i, 2]
  synergy.subset = AK.PD.res[i, 3]
  
  synergy.set.str    = unlist(strsplit(x = synergy.set, split = ","))
  synergy.subset.str = unlist(strsplit(x = synergy.subset, split = ","))
  
  # count models
  synergy.set.models.num = count_models_that_predict_synergies(synergy.set.str, model.predictions)
  synergy.subset.models.num = count_models_that_predict_synergies(synergy.subset.str, model.predictions)
  
  # if too small number of models, skip the diff vector
  if ((synergy.set.models.num <= 3) | (synergy.set.models.num <= 3)) 
    next
  
  # get the diff
  diff.state.ak.pd = get_avg_activity_diff_based_on_synergy_set_cmp(synergy.set.str, synergy.subset.str, model.predictions, models.stable.state)
  diff.link.ak.pd = get_avg_link_operator_diff_based_on_synergy_set_cmp(synergy.set.str, synergy.subset.str, model.predictions, models.link.operator)
  diff.state.list[[paste0(synergy.set, " vs ", synergy.subset)]] = diff.state.ak.pd
  diff.link.list[[paste0(synergy.set, " vs ", synergy.subset)]] = diff.link.ak.pd
}

diff.state.mat = do.call(rbind, diff.state.list)
diff.link.mat = do.call(rbind, diff.link.list)

caption.title.1 = "Table 1: Average activity difference values across all synergy set comparisons (AK-PD)"
datatable(data = diff.state.mat[, c("SRC", "CSK", "MEK_f", "STAT1", "PTPN6")], options = list(
    searching = FALSE, pageLength = 5, lengthMenu = c(5, 10)),
  caption = htmltools::tags$caption(caption.title.1, style="color:#dd4814; font-size: 18px")) %>% 
  formatRound(1:ncol(diff.state.mat), digits = 3)
caption.title.2 = "Table 2: Average link operator difference values across all synergy set comparisons (AK-PD)"
datatable(data = diff.link.mat[, c("SRC", "RAC_f", "MEK_f", "STAT1", "PTEN")], options = list(
    searching = FALSE, pageLength = 5, lengthMenu = c(5, 10)),
  caption = htmltools::tags$caption(caption.title.2, style="color:#dd4814; font-size: 18px")) %>% 
  formatRound(1:ncol(diff.link.mat), digits = 3)

Using the matrix from Table 1 (where we show just \(5\) nodes), we count per network node the number of times that the node’s average activity difference value has surpassed a specified threshold - i.e. the number of times it has been found as important (a biomarker) across all the synergy set comparisons (so the more the better):

threshold = 0.8
biomarker.state.mat = binarize_to_thres(mat = diff.state.mat, threshold)
biomarker.state.counts = colSums(biomarker.state.mat)
pretty_print_vector_names_and_values(table(biomarker.state.counts))

0: 141, 1: 1, 11: 2

The above means that there were \(141\) nodes that were zero times found as activity state biomarkers across all synergy set comparisons, one that was found once and \(2\) that were found \(11\) times (out of a total of 19). These nodes are:

pretty_print_vector_names(biomarker.state.counts[biomarker.state.counts == 11])

2 nodes: SRC, PTPN6

If we visualize the average activity state difference across all synergy comparison sets from Table 1 we can see that the previous \(2\) nodes are more pronounced:

plot_avg_state_diff_graph(net, diff = colMeans(diff.state.mat), layout = nice.layout, 
  title = "Average activity state diff across all synergy subsets (AK-PD)")

Same can be seen with a dotchart where we have excluded the nodes that have zero average activity difference:

df = as.data.frame(t(as.data.frame(as.list(diff.state.ak.pd))))
colnames(df) = "avg.state"
df = df %>% 
  rownames_to_column(var = "nodes") %>%
  filter(avg.state != 0)

ggdotchart(df, x = "nodes", y = "avg.state",
  title = "Average activity state difference across all synergy comparison sets (AK-PD, A498 cell line)", 
  label = "nodes", font.label = list(size = 11, color = "blue"),
  label.select = list(criteria = "`y` >= 0.7 | `y` <= -0.7"), 
  repel = TRUE, label.rectangle = TRUE,
  xlab = "Nodes", ylab = "Average Activity State", 
  ylim = c(-1,1), add = "segments") + 
    font("x.text", size = 10) +
    font("title", size = 11) + 
    geom_hline(yintercept = -0.7, linetype = "dashed", color = "red") + 
    geom_hline(aes(yintercept = 0.7, linetype = "0.7"), color = "red") +
    scale_linetype_manual(name = "Threshold", values = 2, 
      guide = guide_legend(override.aes = list(color = "red"))) + 
    theme(legend.position = c(0.2,0.7))

Note the boolean equation: PTPN6 *= SRC. This means that SRC is the only inhibited node of interest


We will now check if the above observations regarding the activity of the nodes are true using the MCLP dataset as a reference:

mclp.data = read.table(file = "MCLP-v1.1-Level4.tsv", header = TRUE, stringsAsFactors = FALSE)
cell.lines.in.mclp = c("A498", "AGS", "DU145", "COLO205", "SW620", "SF295", "UACC62", "MDAMB468")

We check the phosphorylation value of the SRC_pY527 across all cell lines:

src.data = mclp.data %>% select(Sample_Name, SRC_pY527) %>% na.omit()

# find the activity classes (3 classes: low activity, no activity, high activity)
res = Ckmeans.1d.dp(x = src.data$SRC, k = 3)
activity = as.factor(res$cluster)
levels(activity) = c("low", "medium", "high")
src.data = cbind(src.data, activity)

ggdotchart(data = src.data, x = "Sample_Name", y = "SRC_pY527", 
  title = "SRC_pY527 signaling across all cell lines in MCLP Dataset",
  color = "activity", palette = c("red", "grey", "green"), 
  label = "Sample_Name", label.select = cell.lines.in.mclp, repel = TRUE,
  add = "segments", label.rectangle = TRUE,
  xlab = "Cell Lines", ylab = "SRC_pY527 Signaling",
  add.params = list(color = "activity", palette = c("red", "grey", "green"))) + 
    theme(axis.text.x = element_blank(), axis.ticks = element_blank())

It has been shown in various studies (e.g. in [this paper]https://journals.plos.org/plosone/article?id=10.1371/journal.pone.0071035)) that the phosphorylation sites of SRC include the inhibiting phosphotyrosine 527 site, which overrides the Y416 phosphorylation (which is also tested in the MCLP dataset). Since for A498 cell line we observe one of the largest signaling measurements compared to all the cell lines for the SRC_pY527 site, this means that SRC should be in an inhibiting state, which is exactly what we found from our analysis above.


We also check for the phosphorylation value of the MAPK_pT202Y204 (for the ERK_f activation) across all cell lines:

erk.data = mclp.data %>% select(Sample_Name, MAPK_pT202Y204) %>% na.omit()

# find the activity classes (3 classes: low expression, no expression, high expression)
res = Ckmeans.1d.dp(x = erk.data$MAPK_pT202Y204, k = 3)
activity = as.factor(res$cluster)
levels(activity) = c("low", "medium", "high")
erk.data = cbind(erk.data, activity)

ggdotchart(data = erk.data, x = "Sample_Name", y = "MAPK_pT202Y204", 
  title = "MAPK_pT202Y204 signaling across all cell lines in MCLP Dataset",
  color = "activity", palette = c("red", "grey", "green"), 
  label = "Sample_Name", label.select = cell.lines.in.mclp, repel = TRUE, xlab = "Cell Lines",
  add = "segments", label.rectangle = TRUE, ylab = "MAPK_pT202Y204 Signaling",
  add.params = list(color = "activity", palette = c("red", "grey", "green"))) + 
    theme(axis.text.x = element_blank(), axis.ticks = element_blank())

One of the activating phosphorylation sites of ERK is the ERK1 T202/Y204. From the above figure we see a weak signaling of this phoshosite for the A498 cell line which does not fully correlate well with the results from our analysis above (ERK_f overexpression). Though, in a later section we show that ERK_f is found active in all models from all cell lines that predict the AK-PD synergy (for example even in UACC62 cell line which has a high phoshorylation signal in the above figure).


Using the matrix from Table 2, we count per network node the number of times that the node’s average link operator difference value has surpassed a specified threshold - i.e. the number of times it has been found as important (a biomarker) across all the synergy set comparisons (so the more the better):

biomarker.link.mat = binarize_to_thres(mat = diff.link.mat, thres = 0.8)

biomarker.link.counts = colSums(biomarker.link.mat)
pretty_print_vector_names_and_values(table(biomarker.link.counts))

0: 44, 1: 1, 2: 1, 6: 5, 11: 1

The above means that there were \(44\) nodes that were zero times found as link operator biomarkers across all synergy set comparisons, one that was found once, one that was found twice, \(5\) that were found 6 times and \(1\) that was found 11 times. The last two categories of nodes are:

pretty_print_vector_names(biomarker.link.counts[biomarker.link.counts == 6])

5 nodes: TGFBR2, mTORC1_c, TSC_f, CDC42, RHOA

pretty_print_vector_names(biomarker.link.counts[biomarker.link.counts == 11])

1 node: SRC

If we visualize the average link operator difference across all of the synergy comparison sets from Table 2, we can see the nodes mentioned above:

plot_avg_link_operator_diff_graph(net, diff = colMeans(diff.link.mat), layout = nice.layout, 
  title = "Average link operator diff across all synergy subsets (AK-PD)")

Some notes/observations on the structure of the boolean equations found above:


The boolean equation of the SRC node is: SRC *= ( RTPK_f ) AND/OR NOT ( CSK ) and it’s mean link operator difference value across all synergy set comparisons is -0.5721936 which means that on average the logic operator that binds its two regulators is the AND NOT and thus it’s more difficult to have it as activated (1 case out of 4 in boolean logic). This correlates with the fact that it was found as mostly inhibited in the analysis above. Also note the equations:

  • CSK *= ( PRKACA )
  • PRKACA *= ( ( NFKB_f ) or FOS )
  • FOS *= ( ( ( ERK_f ) or RSK_f ) or SRF )

So, when ERK_f is overexpressed, CSK becomes active, which means that the prevalence of the AND NOT link operator makes the activity of the SRC node dependent only on it’s inhibitor CSK (since it’s active) and not on the activity of RTPK_f node.

  • mTORC1_c *= ( ( RHEB ) or RSK_f ) AND/OR NOT ( AKT1S1 )
  • AKT1S1 *= not ( AKT_f )

mTORC1_c’s mean link operator difference value across all synergy set comparisons is 0.699427 which means that on average the logic operator that binds its two regulators is the OR NOT. This gives the node the structural flexibility to become active when the AK drug is used: AK inhibits AKT_f node, making thus AKT1S1 active, which in turn makes the mTORC1_c equation like this: mTORC1_c *= ( ( RHEB ) or RSK_f ) AND/OR 0. So, an AND link operator would result always in an inhibited mTORC1_c whereas an OR link operator would give the possibility for the mTORC1_c to be active in case one of its activators are active.

We conclude that cancer models in which the AK-PD drug combination is synergistic, tend to also have the nodes SRC and PTPN6 in an inhibited state and the SRC node depends on both it’s regulators, RTPK_f and CSK. The link operators of the mTORC1_c and TSC_f equations also seem to have an important role in the manifestation of this synergy.


Random

We get the average state and link operator differences per network node for the A498 cell line from the random models:

AK.PD.avg.state.diff.random = random.synergy.analysis.res$A498$diff.state.synergies.mat["AK-PD", ]
AK.PD.avg.link.diff.random  = random.synergy.analysis.res$A498$diff.link.synergies.mat["AK-PD", ]

Activity state biomarkers

We will now visualize the nodes average state differences in a network graph. Note that the good models are those that predicted the AK-PD drug combination to be synergistic and were contrasted to those that predicted it to be antagonistic (bad models). The number of models in each category were:

drug.comb = "AK-PD"

good.models.num = sum(random.model.predictions[, drug.comb] == 1 & !is.na(random.model.predictions[, drug.comb]))
# unique good models
# nrow(unique(random.models.link.operator[random.model.predictions[, drug.comb] == 1 & !is.na(random.model.predictions[, drug.comb]),]))
bad.models.num  = sum(random.model.predictions[, drug.comb] == 0 & !is.na(random.model.predictions[, drug.comb]))

pretty_print_string(paste0("Number of 'good' random models (AK-PD synergistic) in the A498 cell line: ", good.models.num))

Number of ‘good’ random models (AK-PD synergistic) in the A498 cell line: 107

pretty_print_string(paste0("Number of 'bad' random models (AK-PD antagonistic) in the A498 cell line: ", bad.models.num))

Number of ‘bad’ random models (AK-PD antagonistic) in the A498 cell line: 3632

plot_avg_state_diff_graph(net, diff = AK.PD.avg.state.diff.random, 
  layout = nice.layout, title = "AK-PD activity state biomarkers (Random models - A498)")

Thus, we can identify the active state biomarkers (note that no inhibited biomarkers at the specified threshold difference level were found):

AK.PD.active.biomarkers = AK.PD.avg.state.diff.random[AK.PD.avg.state.diff.random > 0.8]
pretty_print_vector_names(AK.PD.active.biomarkers)

1 node: ERK_f

The random models that predicted the AK-PD synergy also show an overexpression of the ERK_f node.


Synergy subsets analysis

We perform the same kind of analysis as with the cell-specific models: models that predict a set of synergies will be contrasted to models that predicted the same set with the addition of the extra AK-PD synergy, allowing us thus to refine the activity state and link operator biomarkers we found above from the random models.

We first construct two matrices: in the first, each row is a set vs subset average activity difference vector of the network nodes, while on the second each row is a set vs subset average link operator difference vector of the network nodes:

res = get_synergy_comparison_sets(random.synergy.analysis.res$A498$synergy.subset.stats)
AK.PD.res = res %>% filter(synergies == "AK-PD")

diff.state.list.random = list()
diff.link.list.random = list()
for (i in 1:nrow(AK.PD.res)) {
  synergy.set    = AK.PD.res[i, 2]
  synergy.subset = AK.PD.res[i, 3]
  
  synergy.set.str    = unlist(strsplit(x = synergy.set, split = ","))
  synergy.subset.str = unlist(strsplit(x = synergy.subset, split = ","))
  
  # count models
  synergy.set.models.num = count_models_that_predict_synergies(synergy.set.str, random.model.predictions)
  synergy.subset.models.num = count_models_that_predict_synergies(synergy.subset.str, random.model.predictions)
  # print(paste0(synergy.set.models.num, " ", synergy.subset.models.num))
  
  # if too small number of models, skip the diff vector
  if ((synergy.set.models.num <= 3) | (synergy.set.models.num <= 3)) 
    next
  
  # get the diff
  diff.state.ak.pd = get_avg_activity_diff_based_on_synergy_set_cmp(synergy.set.str, synergy.subset.str, random.model.predictions, random.models.stable.state)
  diff.link.ak.pd = get_avg_link_operator_diff_based_on_synergy_set_cmp(synergy.set.str, synergy.subset.str, random.model.predictions, random.models.link.operator)
  
  diff.state.list.random[[paste0(synergy.set, " vs ", synergy.subset)]] = diff.state.ak.pd
  diff.link.list.random[[paste0(synergy.set, " vs ", synergy.subset)]] = diff.link.ak.pd
}

diff.state.mat.random = do.call(rbind, diff.state.list.random)
diff.link.mat.random = do.call(rbind, diff.link.list.random)

caption.title.3 = "Table 3: Average activity difference values across all synergy set comparisons (AK-PD)"
datatable(data = diff.state.mat.random[, c("SRC", "CSK", "MEK_f", "STAT1", "PTPN6")], options = list(
    searching = FALSE, pageLength = 5, lengthMenu = c(5, 10)),
    caption = htmltools::tags$caption(caption.title.3, style="color:#dd4814; font-size: 18px")) %>% 
      formatRound(1:ncol(diff.state.mat.random), digits = 3)
caption.title.4 = "Table 4: Average link operator difference values across all synergy set comparisons (AK-PD)"
datatable(data = diff.link.mat.random[, c("SRC", "RAC_f", "MEK_f", "STAT1", "PTEN")], options = list(
    searching = FALSE, pageLength = 5, lengthMenu = c(5, 10)),
    caption = htmltools::tags$caption(caption.title.4, style="color:#dd4814; font-size: 18px")) %>% 
      formatRound(1:ncol(diff.link.mat.random), digits = 3)

Using the matrix from Table 3 (where we show just \(5\) nodes), we count per network node the number of times that the node’s average activity difference value has surpassed a specified threshold - i.e. the number of times it has been found as important (a biomarker) across all the synergy set comparisons (so the more the better):

biomarker.state.mat.random = binarize_to_thres(mat = diff.state.mat.random, thres = 0.7)

biomarker.state.counts.random = colSums(biomarker.state.mat.random)
pretty_print_vector_names_and_values(table(biomarker.state.counts.random))

0: 139, 1: 1, 2: 1, 4: 1, 6: 2

So, there are \(2\) nodes that were found as activity state biomarkers \(6\) times across all synergy set comparisons. These nodes are:

pretty_print_vector_names(biomarker.state.counts.random[biomarker.state.counts.random == 6])

2 nodes: PDPK1, PRKCD

But the total number of comparisons was 49 so we could argue that this is not a statistically significant result, which can be seen clearly in the next graph where we visualize the average activity state difference across all synergy comparison sets from Table 3:

plot_avg_state_diff_graph(net, diff = colMeans(diff.state.mat.random), layout = nice.layout, 
  title = "Average activity state diff across all synergy subsets (AK-PD)")

Using the matrix from Table 4, we count per network node the number of times that the node’s average link operator difference value has surpassed a specified threshold - i.e. the number of times it has been found as important (a biomarker) across all the synergy set comparisons (so the more the better):

biomarker.link.mat.random = binarize_to_thres(mat = diff.link.mat.random, thres = 0.7)

biomarker.link.counts.random = colSums(biomarker.link.mat.random)
pretty_print_vector_names_and_values(table(biomarker.link.counts.random))

0: 50, 1: 1, 7: 1

So, there was a node that was found \(7\) times (out of a total of 49, so not statistically significant) as a link operator biomarkers across all synergy set comparisons:

pretty_print_vector_names(biomarker.link.counts.random[biomarker.link.counts.random == 7])

1 node: mTORC1_c

We visualize the average link operator difference across all synergy comparison sets from Table 4:

plot_avg_link_operator_diff_graph(net, diff = colMeans(diff.link.mat.random), layout = nice.layout, 
  title = "Average link operator diff across all synergy subsets (AK-PD)")

AK-PD in other cell lines

Though the AK-PD synergy was observed and predicted in the A498 model dataset, we investigate its biomarkers in the other cell lines where it was predicted as a False Positive (FP) synergy (predicted by the models but not observed in the experiments). Thus, we can still see if there are any common (activity state and link operator) biomarkers for AK-PD across the all the cell-specific models by contrasting in each cell line the models that predicted AK-PD vs the models that did not:

drug.comb = "AK-PD"

ak.pd.diff.state.list = list()
ak.pd.diff.link.list = list()

for (cell.line in cell.lines) {
  if (cell.line == "A498") 
    next
  
  ak.pd.diff.state.list[[cell.line]] = get_avg_activity_diff_based_on_specific_synergy_prediction(
    model.predictions = model.predictions.per.cell.line[[cell.line]], 
    models.stable.state = models.stable.state.per.cell.line[[cell.line]], 
    drug.comb)
  ak.pd.diff.link.list[[cell.line]] = get_avg_link_operator_diff_based_on_specific_synergy_prediction(
    model.predictions = model.predictions.per.cell.line[[cell.line]], 
    models.link.operator = models.link.operators.per.cell.line[[cell.line]], 
    drug.comb)
  # print(count_models_that_predict_synergies(drug.comb, model.predictions = model.predictions.per.cell.line[[cell.line]]))
}

ak.pd.diff.state.mat = do.call(rbind, ak.pd.diff.state.list)
ak.pd.diff.link.mat = do.call(rbind, ak.pd.diff.link.list)

ERK_f was the one node that was found as an activity state & link operator AK-PD biomarker in all cell lines

ak.pd.biomarker.state.mat = binarize_to_thres(mat = ak.pd.diff.state.mat, thres = 0.7)
ak.pd.biomarker.link.mat  = binarize_to_thres(mat = ak.pd.diff.link.mat, thres = 0.7)

ak.pd.biomarker.state.mat.counts = colSums(ak.pd.biomarker.state.mat)
ak.pd.biomarker.link.mat.counts = colSums(ak.pd.biomarker.link.mat)

#pretty_print_vector_names_and_values(table(ak.pd.biomarker.state.mat.counts))
#pretty_print_vector_names_and_values(table(ak.pd.biomarker.link.mat.counts))

pretty_print_vector_names(ak.pd.biomarker.state.mat.counts[ak.pd.biomarker.state.mat.counts == 7])

1 node: ERK_f

pretty_print_vector_names(ak.pd.biomarker.link.mat.counts[ak.pd.biomarker.link.mat.counts == 6])

1 node: ERK_f

R session info

xfun::session_info()
R version 3.6.1 (2019-07-05)
Platform: x86_64-pc-linux-gnu (64-bit)
Running under: Ubuntu 18.04.3 LTS, RStudio 1.2.5001

Locale:
  LC_CTYPE=en_US.UTF-8       LC_NUMERIC=C               LC_TIME=en_US.UTF-8       
  LC_COLLATE=en_US.UTF-8     LC_MONETARY=en_US.UTF-8    LC_MESSAGES=en_US.UTF-8   
  LC_PAPER=en_US.UTF-8       LC_NAME=C                  LC_ADDRESS=C              
  LC_TELEPHONE=C             LC_MEASUREMENT=en_US.UTF-8 LC_IDENTIFICATION=C       

Package version:
  assertthat_0.2.1     backports_1.1.5      base64enc_0.1-3      BH_1.69.0.1         
  bibtex_0.4.2         circlize_0.4.8       Ckmeans.1d.dp_4.3.0  cli_1.1.0           
  clue_0.3-57          cluster_2.1.0        codetools_0.2-16     colorspace_1.4-1    
  compiler_3.6.1       ComplexHeatmap_2.0.0 cowplot_1.0.0        crayon_1.3.4        
  crosstalk_1.0.0      digest_0.6.23        dplyr_0.8.3          DT_0.10             
  ellipsis_0.3.0       emba_0.1.2           evaluate_0.14        fansi_0.4.0         
  farver_2.0.1         fastmap_1.0.1        gbRd_0.4-11          GetoptLong_0.1.7    
  ggplot2_3.2.1        ggpubr_0.2.4         ggrepel_0.8.1        ggsci_2.9           
  ggsignif_0.6.0       GlobalOptions_0.1.1  glue_1.3.1           graphics_3.6.1      
  grDevices_3.6.1      grid_3.6.1           gridExtra_2.3        gtable_0.3.0        
  highr_0.8            htmltools_0.4.0      htmlwidgets_1.5.1    httpuv_1.5.2        
  igraph_1.2.4.1       jsonlite_1.6         knitr_1.26           labeling_0.3        
  later_1.0.0          lattice_0.20.38      lazyeval_0.2.2       lifecycle_0.1.0     
  magrittr_1.5         markdown_1.1         MASS_7.3.51.4        Matrix_1.2.17       
  methods_3.6.1        mgcv_1.8.31          mime_0.7             munsell_0.5.0       
  nlme_3.1.142         packrat_0.5.0        parallel_3.6.1       pillar_1.4.2        
  pkgconfig_2.0.3      plogr_0.2.0          plyr_1.8.4           png_0.1-7           
  polynom_1.4.0        promises_1.1.0       purrr_0.3.3          R6_2.4.1            
  RColorBrewer_1.1-2   Rcpp_1.0.3           Rdpack_0.11-0        reshape2_1.4.3      
  rje_1.10.10          rjson_0.2.20         rlang_0.4.2          rmarkdown_1.17      
  rstudioapi_0.10      scales_1.1.0         shape_1.4.4          shiny_1.4.0         
  sourcetools_0.1.7    splines_3.6.1        stats_3.6.1          stringi_1.4.3       
  stringr_1.4.0        tibble_2.1.3         tidyr_1.0.0          tidyselect_0.2.5    
  tinytex_0.17         tools_3.6.1          usefun_0.4.3         utf8_1.1.4          
  utils_3.6.1          vctrs_0.2.0          viridisLite_0.3.0    visNetwork_2.0.8    
  withr_2.1.2          xfun_0.11            xtable_1.8-4         yaml_2.2.0          
  zeallot_0.1.0       
LS0tCnRpdGxlOiAiQ2FzY2FkZSBTeW5lcmd5IEJpb21hcmtlcnMgZm9yIEFLLVBELCBQRC1QSSwgUEQtRzIsIFBJLUQxIHN5bmVyZ2llcyIKYXV0aG9yOiAiW0pvaG4gWm9ib2xhc10oaHR0cHM6Ly9naXRodWIuY29tL2JibG9kZm9uKSIKZGF0ZTogIkxhc3QgdXBkYXRlZDogYHIgZm9ybWF0KFN5cy50aW1lKCksICclZCAlQiwgJVknKWAiCm91dHB1dDoKICBodG1sX2RvY3VtZW50OgogICAgY3NzOiBzdHlsZS5jc3MKICAgIHRoZW1lOiB1bml0ZWQKICAgIHRvYzogdHJ1ZQogICAgdG9jX2Zsb2F0OgogICAgICBjb2xsYXBzZWQ6IGZhbHNlCiAgICAgIHNtb290aF9zY3JvbGw6IHRydWUKICAgIHRvY19kZXB0aDogMwogICAgbnVtYmVyX3NlY3Rpb25zOiBmYWxzZQogICAgY29kZV9mb2xkaW5nOiBoaWRlCiAgICBjb2RlX2Rvd25sb2FkOiB0cnVlCi0tLQoKYGBge3IgUmVuZGVyIGNvbW1hbmQsIGV2YWw9RkFMU0UsIGluY2x1ZGU9RkFMU0V9CiNybWFya2Rvd246OnJlbmRlcihpbnB1dCA9ICIuL2Nhc2NhZGVfc3luZXJneV9iaW9tYXJrZXJzLlJtZCIsIG91dHB1dF9mb3JtYXQgPSAiaHRtbF9kb2N1bWVudCIsIG91dHB1dF9kaXIgPSAiLi4vLi4vZG9jcy9jYXNjYWRlL2NlbGwtbGluZXMtMjUwMC8iKQpgYGAKCiMjIEludHJvIHstfQoKVGhlIHB1cnBvc2Ugb2YgdGhpcyBhbmFseXNpcyBpcyB0byBmaW5kIHN5bmVyZ3kgYmlvbWFya2VycyB1c2luZyB0aGUgW2VtYmEgUiBwYWNrYWdlXShodHRwczovL2dpdGh1Yi5jb20vYmJsb2Rmb24vZW1iYSkgaW4gdGhlIGNhc2NhZGUgYm9vbGVhbiBtb2RlbCBkYXRhc2V0cyAodGhlICpjYXNjYWRlKiBpcyB0aGUgbmFtZSBvZiB0aGUgdG9wb2xvZ3kgdXNlZCkuIApQYXJ0aWN1bGFybHksIHdlIHdpbGwgaW52ZXN0aWdhdGUgJDQkIHN5bmVyZ2llczogYEFLLVBEYCwgYFBELVBJYCwgYFBELUcyYCwgYFBJLUQxYCBhbmQgdHJ5IHRvIGZpbmQgY2F1c2FsIG1lY2hhbmlzbXMgdGhhdCBtaWdodCBleHBsYWluIHdoeSBhbmQgd2hlcmUgKHBhdGh3YXlzKSB0aGVzZSBzeW5lcmdpZXMgbWFuaWZlc3QuIAoKTm90ZSB0aGF0IGBBS2AgaXMgYW4gKipBS1QgaW5oaWJpdG9yKiogYW5kIGBQSWAgaXMgYSAqKlBJM0sgaW5oaWJpdG9yKiogYW5kIHRoZXkgYm90aCB0YXJnZXQgdGhlICoqUEkzSy9BS1QvbVRPUiBwYXRod2F5KiouIFRoZSBjb21iaW5hdGlvbiBvZiBzdWNoIGFuIGluaGliaXRvciB3aXRoIGEgKipNRUsgaW5oaWJpdG9yKiogLSBgUERgIC0gdGhhdCB0YXJnZXRzIHRoZSAqKk1BUEsvRVJLIHBhdGh3YXkqKiwgaGFzIHByb3ZlbiB0byBiZSBtb3JlIGVmZmVjdGl2ZSB0aGFuIHRoZSBzaW5nbGUgZHJ1ZyB0cmVhdG1lbnQgZHVyaW5nIGNsaW5pY2FsIHRyaWFscyB3aXRoIHBhdGllbnRzIHRoYXQgaGFkIGFkdmFuY2VkIGNvbG9yZWN0YWwgY2FyY2lub21hIChbc291cmNlXShodHRwczovL2NsaW5pY2FsdHJpYWxzLmdvdi9jdDIvc2hvdy9OQ1QwMTMzMzQ3NSkpLiAKVGhlIGBHMmAgZHJ1ZyBpcyBhICoqUERQSzEgaW5oaWJpdG9yKiogYW5kIHRoZSBgRDFgIGRydWcgaXMgKiphbiBpbmhpYml0b3Igb2YgYFJTS2AgaXNvZm9ybXMqKiAoClJQUzZLQTEsIFJQUzZLQTMsIFJQUzZLQTIsIFJQUzZLQTYpCgpUaGUgYm9vbGVhbiBtb2RlbCBkYXRhc2V0cyBhcmUgaW4gdG90YWwgJDkkOiBvbmUgZm9yIGVhY2ggY2VsbCBsaW5lIApvZiBpbnRlcmVzdCAoOCBjZWxsIGxpbmVzKSB3aGVyZSB0aGUgbW9kZWxzIHdlcmUgKipmaXR0ZWQgdG8gYSBzcGVjaWZpYyBzdGVhZHkgc3RhdGUqKiBpbiBlYWNoIApjYXNlIGFuZCBvbmUgZm9yIHRoZSBzby1jYWxsZWQgKipyYW5kb20gbW9kZWxzKiogd2hpY2ggd2VyZSBnZW5lcmF0ZWQgKnJhbmRvbWx5KiBpbiAKdGhlIHNlbnNlIHRoYXQgd2VyZSBmaXR0ZWQgb25seSB0byBhIHByb2xpZmVyYXRpb24gc3RhdGUgKHNpbXVsYXRpb25zIHdlcmUgZG9uZSB1c2luZyAKdGhlIERydWdMb2dpY3Mgc29mdHdhcmUgbW9kdWxlcyBgR2l0c2JlYCBhbmQgYERyYWJtZWApLgoKIyMgSW5wdXQgey19CgpMb2FkaW5nIGxpYnJhcmllczoKYGBge3IgTG9hZCBsaWJyYXJpZXMsIG1lc3NhZ2UgPSBGQUxTRX0KbGlicmFyeShlbWJhKQpsaWJyYXJ5KHVzZWZ1bikKbGlicmFyeShDb21wbGV4SGVhdG1hcCkKbGlicmFyeShjaXJjbGl6ZSkKbGlicmFyeShkcGx5cikKbGlicmFyeSh0aWJibGUpCmxpYnJhcnkoRFQpCmxpYnJhcnkoZ2dwdWJyKQpsaWJyYXJ5KENrbWVhbnMuMWQuZHApCmBgYAoKV2UgbG9hZCB0aGUgY2VsbC1zcGVjaWZpYyBpbnB1dCBkYXRhOgpgYGB7ciBDZWxsLXNwZWNpZmljIElucHV0LCBjYWNoZT1UUlVFfQojIENlbGwgTGluZXMKY2VsbC5saW5lcyA9IGMoIkE0OTgiLCAiQUdTIiwgIkRVMTQ1IiwgImNvbG8yMDUiLCAiU1c2MjAiLCAiU0YyOTUiLCAiVUFDQzYyIiwgIk1EQS1NQi00NjgiKQoKY2VsbC5saW5lLmRpcnMgPSBzYXBwbHkoY2VsbC5saW5lcywgZnVuY3Rpb24oY2VsbC5saW5lKSB7CiAgcGFzdGUwKGdldHdkKCksICIvIiwgY2VsbC5saW5lKQp9KQoKIyBNb2RlbCBwcmVkaWN0aW9ucwptb2RlbC5wcmVkaWN0aW9ucy5maWxlcyA9IHNhcHBseShjZWxsLmxpbmUuZGlycywgZnVuY3Rpb24oY2VsbC5saW5lLmRpcikgewogIHBhc3RlMChjZWxsLmxpbmUuZGlyLCAiL21vZGVsX3ByZWRpY3Rpb25zIikKfSkKCm1vZGVsLnByZWRpY3Rpb25zLnBlci5jZWxsLmxpbmUgPSBsYXBwbHkobW9kZWwucHJlZGljdGlvbnMuZmlsZXMsIGZ1bmN0aW9uKGZpbGUpIHsgICAKICBnZXRfbW9kZWxfcHJlZGljdGlvbnMoZmlsZSkgCn0pCgojIE9ic2VydmVkIHN5bmVyZ2llcwpvYnNlcnZlZC5zeW5lcmdpZXMuZmlsZXMgPSBzYXBwbHkoY2VsbC5saW5lLmRpcnMsIGZ1bmN0aW9uKGNlbGwubGluZS5kaXIpIHsKICBwYXN0ZTAoY2VsbC5saW5lLmRpciwgIi9vYnNlcnZlZF9zeW5lcmdpZXMiKQp9KQoKb2JzZXJ2ZWQuc3luZXJnaWVzLnBlci5jZWxsLmxpbmUgPSBsYXBwbHkob2JzZXJ2ZWQuc3luZXJnaWVzLmZpbGVzLCBmdW5jdGlvbihmaWxlKSB7CiAgZ2V0X29ic2VydmVkX3N5bmVyZ2llcyhmaWxlKQp9KQoKIyBNb2RlbHMgU3RhYmxlIFN0YXRlICgxIHBlciBtb2RlbCkKbW9kZWxzLnN0YWJsZS5zdGF0ZS5maWxlcyA9IHNhcHBseShjZWxsLmxpbmUuZGlycywgZnVuY3Rpb24oY2VsbC5saW5lLmRpcikgewogIHBhc3RlMChjZWxsLmxpbmUuZGlyLCAiL21vZGVsc19zdGFibGVfc3RhdGUiKQp9KQoKbW9kZWxzLnN0YWJsZS5zdGF0ZS5wZXIuY2VsbC5saW5lID0gbGFwcGx5KG1vZGVscy5zdGFibGUuc3RhdGUuZmlsZXMsIGZ1bmN0aW9uKGZpbGUpIHsgCiAgYXMubWF0cml4KHJlYWQudGFibGUoZmlsZSwgY2hlY2submFtZXMgPSBGQUxTRSkpCn0pCgojIE1vZGVscyBMaW5rIE9wZXJhdG9ycwptb2RlbHMubGluay5vcGVyYXRvci5maWxlcyA9IHNhcHBseShjZWxsLmxpbmUuZGlycywgZnVuY3Rpb24oY2VsbC5saW5lLmRpcikgewogIHBhc3RlMChjZWxsLmxpbmUuZGlyLCAiL21vZGVsc19saW5rX29wZXJhdG9yIikKfSkKCm1vZGVscy5saW5rLm9wZXJhdG9ycy5wZXIuY2VsbC5saW5lID0gbGFwcGx5KG1vZGVscy5saW5rLm9wZXJhdG9yLmZpbGVzLCBmdW5jdGlvbihmaWxlKSB7CiAgYXMubWF0cml4KHJlYWQudGFibGUoZmlsZSwgY2hlY2submFtZXMgPSBGQUxTRSkpCn0pCmBgYAoKVGhlIHJhbmRvbSBtb2RlbCBpbnB1dCBkYXRhOgpgYGB7ciBSYW5kb20gbW9kZWwgSW5wdXR9CnJhbmRvbS5kaXIgPSBwYXN0ZTAoZ2V0d2QoKSwgIi9yYW5kb20iKQpyYW5kb20ubW9kZWwucHJlZGljdGlvbnMgPSBnZXRfbW9kZWxfcHJlZGljdGlvbnMocGFzdGUwKHJhbmRvbS5kaXIsICIvbW9kZWxfcHJlZGljdGlvbnMiKSkKCnJhbmRvbS5tb2RlbHMuc3RhYmxlLnN0YXRlID0gYXMubWF0cml4KAogIHJlYWQudGFibGUoZmlsZSA9IHBhc3RlMChyYW5kb20uZGlyLCAiL21vZGVsc19zdGFibGVfc3RhdGUiKSwgY2hlY2submFtZXMgPSBGQUxTRSkKKQoKcmFuZG9tLm1vZGVscy5saW5rLm9wZXJhdG9yID0KICBhcy5tYXRyaXgocmVhZC50YWJsZShmaWxlID0gcGFzdGUwKHJhbmRvbS5kaXIsICIvbW9kZWxzX2xpbmtfb3BlcmF0b3IiKSwgY2hlY2submFtZXMgPSBGQUxTRSkpCgojIHRoZSBub2RlIG5hbWVzIHVzZWQgaW4gb3VyIGFuYWx5c2lzCm5vZGUubmFtZXMgPSBjb2xuYW1lcyhyYW5kb20ubW9kZWxzLnN0YWJsZS5zdGF0ZSkKIyB0aGUgdGVzdGVkIGRydWcgY29tdGlvbnMKZHJ1Zy5jb21ib3MgPSBjb2xuYW1lcyhyYW5kb20ubW9kZWwucHJlZGljdGlvbnMpCmBgYAoKIyMgU3luZXJneSBCaW9tYXJrZXIgQW5hbHlzaXMgey19CgpVc2luZyB0aGUgZ2VuZXJpYyBmdW5jdGlvbiBgYmlvbWFya2VyX3N5bmVyZ3lfYW5hbHlzaXNgIGZyb20gdGhlIFtlbWJhIFIgcGFja2FnZV0oaHR0cHM6Ly9naXRodWIuY29tL2JibG9kZm9uL2VtYmEpLCB3ZSBjYW4gZmluZAoqc3luZXJneSBiaW9tYXJrZXJzKiBpLmUuIG5vZGVzIHdob3NlIGFjdGl2aXR5IGFuZCBib29sZWFuIGVxdWF0aW9uIHBhcmFtZXRlcml6YXRpb24gKGxpbmsgb3BlcmF0b3IpIGFmZmVjdCB0aGUgbWFuaWZlc3RhdGlvbiBvZiBzeW5lcmdpZXMgaW4gdGhlIGJvb2xlYW4gbW9kZWxzLiBNb2RlbHMgYXJlIGNsYXNzaWZpZWQgYmFzZWQgb24gd2hldGhlciB0aGV5IHByZWRpY3Qgb3Igbm90IGVhY2ggb2YgdGhlIHByZWRpY3RlZCBzeW5lcmdpZXMgZm91bmQgaW4gZWFjaCBib29sZWFuIGRhdGFzZXQuCgpGaXJzdCB3ZSBydW4gdGhlIGFuYWx5c2lzIG9uIHRoZSAqKmNlbGwtc3BlY2lmaWMgYm9vbGVhbiBtb2RlbCBkYXRhc2V0cyoqIChub3RlIAp0aGF0IGV2ZXJ5IGlucHV0IHRvIHRoZSBgYmlvbWFya2VyX3N5bmVyZ3lfYW5hbHlzaXNgIGZ1bmN0aW9uIGNoYW5nZXMgcGVyIGNlbGwgbGluZSk6CmBgYHtyIENlbGwtc3BlY2lmaWMgTW9kZWxzIC0gU3luZXJneSBCaW9tYXJrZXIgQW5hbHlzaXMsIGNhY2hlID0gVFJVRX0KY2VsbC5zcGVjaWZpYy5zeW5lcmd5LmFuYWx5c2lzLnJlcyA9IGxpc3QoKQoKZm9yIChjZWxsLmxpbmUgaW4gY2VsbC5saW5lcykgewogIGNlbGwuc3BlY2lmaWMuc3luZXJneS5hbmFseXNpcy5yZXNbW2NlbGwubGluZV1dID0KICAgIGJpb21hcmtlcl9zeW5lcmd5X2FuYWx5c2lzKG1vZGVsLnByZWRpY3Rpb25zLnBlci5jZWxsLmxpbmVbW2NlbGwubGluZV1dLAogICAgICBtb2RlbHMuc3RhYmxlLnN0YXRlLnBlci5jZWxsLmxpbmVbW2NlbGwubGluZV1dLCAKICAgICAgbW9kZWxzLmxpbmsub3BlcmF0b3JzLnBlci5jZWxsLmxpbmVbW2NlbGwubGluZV1dLAogICAgICBvYnNlcnZlZC5zeW5lcmdpZXMucGVyLmNlbGwubGluZVtbY2VsbC5saW5lXV0sIHRocmVzaG9sZCA9IDAuNykKfQpgYGAKCk5leHQgd2UgcnVuIHRoZSBhbmFseXNpcyBvbiB0aGUgKipyYW5kb20gYm9vbGVhbiBtb2RlbCBkYXRhc2V0cyoqIChub3RlIAp0aGF0IHRoZSBvbmx5IGlucHV0IHRvIHRoZSBgYmlvbWFya2VyX3N5bmVyZ3lfYW5hbHlzaXNgIGZ1bmN0aW9uIHRoYXQgY2hhbmdlcwppcyB0aGUgb2JzZXJ2ZWQgc3luZXJnaWVzIHBlciBjZWxsIGxpbmUgLSB0aGUgcmVzdCBpcyB0aGUgc2FtZSBkYXRhIGZyb20gdGhlIApyYW5kb20gbW9kZWwgZGF0YXNldCk6CmBgYHtyIFJhbmRvbSBNb2RlbHMgLSBTeW5lcmd5IEJpb21hcmtlciBBbmFseXNpcywgY2FjaGUgPSBUUlVFfQojIFN5bmVyZ3kgQmlvbWFya2VycyBmb3IgY2VsbCBwcm9saWZlcmF0aW9uIG1vZGVscwpyYW5kb20uc3luZXJneS5hbmFseXNpcy5yZXMgPSBsaXN0KCkKCmZvciAoY2VsbC5saW5lIGluIGNlbGwubGluZXMpIHsKICByYW5kb20uc3luZXJneS5hbmFseXNpcy5yZXNbW2NlbGwubGluZV1dID0KICAgIGJpb21hcmtlcl9zeW5lcmd5X2FuYWx5c2lzKHJhbmRvbS5tb2RlbC5wcmVkaWN0aW9ucywgCiAgICAgIHJhbmRvbS5tb2RlbHMuc3RhYmxlLnN0YXRlLCByYW5kb20ubW9kZWxzLmxpbmsub3BlcmF0b3IsIAogICAgICBvYnNlcnZlZC5zeW5lcmdpZXMucGVyLmNlbGwubGluZVtbY2VsbC5saW5lXV0sIHRocmVzaG9sZCA9IDAuNykKfQpgYGAKCiMjIE9ic2VydmVkIHN5bmVyZ2llcyB7LX0KCkVhY2ggb2YgdGhlIGNlbGwgbGluZXMgc3R1ZGllZCBoYXMgYSBkaWZmZXJlbnQgc2V0IG9mIG9ic2VydmVkIHN5bmVyZ2llcyAoZHJ1ZyAKY29tdGlvbnMgdGhhdCB3ZXJlIGZvdW5kIHN5bmVyZ2lzdGljIGFjcm9zcyBhbGwgdGhlIApgciBsZW5ndGgoZHJ1Zy5jb21ib3MpYCB0ZXN0ZWQgb25lcykuIEluIHRoaXMgc2VjdGlvbiwgd2Ugd2lsbCAKKip2aXN1YWxpemUgdGhlIGNlbGwgbGluZXMnIG9ic2VydmVkIHN5bmVyZ2llcyBhbmQgbWFyayB0aGUgc3luZXJnaWVzIHRoYXQgd2VyZSAKYWxzbyBwcmVkaWN0ZWQgYnkgdGhlIGNlbGwtc3BlY2lmaWMgbW9kZWxzIGFuZCB0aGUgcmFuZG9tLWdlbmVyYXRlZCBvbmVzKiouIApGaXJzdCwgd2UgZ2V0IHRoZSBiaW9tYXJrZXJzIGZvciB0aGVzZSBzeW5lcmdpZXMgZnJvbSBlYWNoIGNlbGwgbGluZToKYGBge3IgVG90YWwgcHJlZGljdGVkIHN5bmVyZ2llcyBieSB0aGUgY2VsbC1zcGVjaWZpYyBtb2RlbHN9CnRvdGFsLnByZWRpY3RlZC5zeW5lcmdpZXMuY2VsbC5zcGVjaWZpYyA9CiAgdW5pcXVlKHVubGlzdChzYXBwbHkoY2VsbC5zcGVjaWZpYy5zeW5lcmd5LmFuYWx5c2lzLnJlcywgZnVuY3Rpb24oeCkgeyB4JHByZWRpY3RlZC5zeW5lcmdpZXN9KSkpCnRvdGFsLnByZWRpY3RlZC5zeW5lcmdpZXMuY2VsbC5zcGVjaWZpYy5udW0gPSBsZW5ndGgodG90YWwucHJlZGljdGVkLnN5bmVyZ2llcy5jZWxsLnNwZWNpZmljKQpgYGAKClRoZSBzYW1lIGZvciB0aGUgcmFuZG9tIG1vZGVsczoKYGBge3IgVG90YWwgcHJlZGljdGVkIHN5bmVyZ2llcyBieSB0aGUgcmFuZG9tIG1vZGVsc30KdG90YWwucHJlZGljdGVkLnN5bmVyZ2llcy5yYW5kb20gPSAKICB1bmlxdWUodW5saXN0KHNhcHBseShyYW5kb20uc3luZXJneS5hbmFseXNpcy5yZXMsIGZ1bmN0aW9uKHgpIHsgeCRwcmVkaWN0ZWQuc3luZXJnaWVzfSkpKQp0b3RhbC5wcmVkaWN0ZWQuc3luZXJnaWVzLnJhbmRvbS5udW0gPSBsZW5ndGgodG90YWwucHJlZGljdGVkLnN5bmVyZ2llcy5yYW5kb20pCmBgYAoKVGhlbiwgd2UgZ2V0IHRoZSBvYnNlcnZlZCBzeW5lcmdpZXMgZnJvbSBlYWNoIGNlbGwgbGluZSBpbiBhIGBkYXRhLmZyYW1lYDoKYGBge3IgT2JzZXJ2ZWQgc3luZXJnaWVzIHBlciBjZWxsIGxpbmV9Cm9ic2VydmVkLnN5bmVyZ2llcy5yZXMgPSBnZXRfb2JzZXJ2ZWRfc3luZXJnaWVzX3Blcl9jZWxsX2xpbmUoY2VsbC5saW5lLmRpcnMsIGRydWcuY29tYm9zKQoKIyByZW1vdmUgZHJ1ZyBjb210aW9ucyB3aGljaCBhcmUgbm90IG9ic2VydmVkIGluIGFueSBvZiB0aGUgY2VsbCBsaW5lcwpvYnNlcnZlZC5zeW5lcmdpZXMucmVzID0gcHJ1bmVfY29sdW1uc19mcm9tX2RmKG9ic2VydmVkLnN5bmVyZ2llcy5yZXMsIHZhbHVlID0gMCkKCnRvdGFsLm9ic2VydmVkLnN5bmVyZ2llcyA9IGNvbG5hbWVzKG9ic2VydmVkLnN5bmVyZ2llcy5yZXMpCnRvdGFsLm9ic2VydmVkLnN5bmVyZ2llcy5udW0gPSBsZW5ndGgodG90YWwub2JzZXJ2ZWQuc3luZXJnaWVzKQpgYGAKCkxhc3RseSwgd2UgdmlzdWFsaXplIHRoZSBvYnNlcnZlZCBhbmQgcHJlZGljdGVkIHN5bmVyZ2llcyBmb3IgYWxsIGNlbGwgbGluZXMgCmluIG9uZSBoZWF0bWFwOgpgYGB7ciBPYnNlcnZlZCBzeW5lcmdpZXMgaGVhdG1hcCwgZmlnLmhlaWdodCA9IDUsIGZpZy53aWR0aCA9IDksIGRwaSA9IDMwMCwgY2FjaGUgPSBUUlVFfQojIGNvbG9yIHRoZSBjZWxsLXNwZWNpZmljIHByZWRpY3RlZCBzeW5lcmdpZXMKcHJlZGljdGVkLnN5bmVyZ2llcy5jb2xvcnMgPSByZXAoImJsYWNrIiwgdG90YWwub2JzZXJ2ZWQuc3luZXJnaWVzLm51bSkKbmFtZXMocHJlZGljdGVkLnN5bmVyZ2llcy5jb2xvcnMpID0gdG90YWwub2JzZXJ2ZWQuc3luZXJnaWVzCmNvbW1vbi5wcmVkaWN0ZWQuc3luZXJnaWVzID0gaW50ZXJzZWN0KHRvdGFsLnByZWRpY3RlZC5zeW5lcmdpZXMuY2VsbC5zcGVjaWZpYywKICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgdG90YWwucHJlZGljdGVkLnN5bmVyZ2llcy5yYW5kb20pCmNlbGwuc3BlY2lmaWMub25seS5wcmVkaWN0ZWQuc3luZXJnaWVzID0gCiAgdG90YWwucHJlZGljdGVkLnN5bmVyZ2llcy5jZWxsLnNwZWNpZmljWyF0b3RhbC5wcmVkaWN0ZWQuc3luZXJnaWVzLmNlbGwuc3BlY2lmaWMgJWluJSB0b3RhbC5wcmVkaWN0ZWQuc3luZXJnaWVzLnJhbmRvbV0KcmFuZG9tLm9ubHkucHJlZGljdGVkLnN5bmVyZ2llcyA9IAogIHRvdGFsLnByZWRpY3RlZC5zeW5lcmdpZXMucmFuZG9tWyF0b3RhbC5wcmVkaWN0ZWQuc3luZXJnaWVzLnJhbmRvbSAlaW4lIHRvdGFsLnByZWRpY3RlZC5zeW5lcmdpZXMuY2VsbC5zcGVjaWZpY10KCnByZWRpY3RlZC5zeW5lcmdpZXMuY29sb3JzW3RvdGFsLm9ic2VydmVkLnN5bmVyZ2llcyAlaW4lIAogICAgICAgICAgICAgICAgICAgICAgICAgICBjb21tb24ucHJlZGljdGVkLnN5bmVyZ2llc10gPSAiYmx1ZSIKcHJlZGljdGVkLnN5bmVyZ2llcy5jb2xvcnNbdG90YWwub2JzZXJ2ZWQuc3luZXJnaWVzICVpbiUgCiAgICAgICAgICAgICAgICAgICAgICAgICAgIGNlbGwuc3BlY2lmaWMub25seS5wcmVkaWN0ZWQuc3luZXJnaWVzXSA9ICJvcmFuZ2UiCnByZWRpY3RlZC5zeW5lcmdpZXMuY29sb3JzW3RvdGFsLm9ic2VydmVkLnN5bmVyZ2llcyAlaW4lIAogICAgICAgICAgICAgICAgICAgICAgICAgICByYW5kb20ub25seS5wcmVkaWN0ZWQuc3luZXJnaWVzXSA9ICJwdXJwbGUiCgojIGRlZmluZSBhIGNvbG9yaW5nCm9icy5zeW5lcmdpZXMuY29sLmZ1biA9IGNvbG9yUmFtcDIoYygwLCAxKSwgYygicmVkIiwgImdyZWVuIikpCgpvYnNlcnZlZC5zeW5lcmdpZXMuaGVhdG1hcCA9IAogIEhlYXRtYXAobWF0cml4ID0gYXMubWF0cml4KG9ic2VydmVkLnN5bmVyZ2llcy5yZXMpLCAKICAgICAgICAgIGNvbCA9IG9icy5zeW5lcmdpZXMuY29sLmZ1biwKICAgICAgICAgIGNvbHVtbl90aXRsZSA9ICJPYnNlcnZlZCBzeW5lcmdpZXMgcGVyIGNlbGwgbGluZSIsCiAgICAgICAgICBjb2x1bW5fdGl0bGVfZ3AgPSBncGFyKGZvbnRzaXplID0gMjApLAogICAgICAgICAgY29sdW1uX25hbWVzX2dwID0gZ3Bhcihjb2wgPSBwcmVkaWN0ZWQuc3luZXJnaWVzLmNvbG9ycyksCiAgICAgICAgICByb3dfdGl0bGUgPSAiQ2VsbCBMaW5lcyIsIHJvd190aXRsZV9zaWRlID0gImxlZnQiLAogICAgICAgICAgcm93X2RlbmRfc2lkZSA9ICJyaWdodCIsIHJvd19uYW1lc19zaWRlID0gImxlZnQiLAogICAgICAgICAgcmVjdF9ncCA9IGdwYXIoY29sID0gImJsYWNrIiwgbHdkID0gMC4zKSwKICAgICAgICAgIGhlYXRtYXBfbGVnZW5kX3BhcmFtID0gbGlzdChhdCA9IGMoMSwgMCksIGxhYmVscyA9IGMoIllFUyIsICJOTyIpLCAKICAgICAgICAgICAgY29sb3JfYmFyID0gImRpc2NyZXRlIiwgdGl0bGUgPSAiT2JzZXJ2ZWQiLCBkaXJlY3Rpb24gPSAidmVydGljYWwiKSkKCmxnZCA9IExlZ2VuZChhdCA9IGMoIkNlbGwtc3BlY2lmaWMiLCAiUmFuZG9tIiwgIkJvdGgiKSwgdGl0bGUgPSAiUHJlZGljdGVkIiwgCiAgICAgICAgICAgICBsZWdlbmRfZ3AgPSBncGFyKGZpbGwgPSBjKCJvcmFuZ2UiLCAicHVycGxlIiwgImJsdWUiKSkpCgpkcmF3KG9ic2VydmVkLnN5bmVyZ2llcy5oZWF0bWFwLCAgaGVhdG1hcF9sZWdlbmRfbGlzdCA9IGxpc3QobGdkKSwgCiAgICAgaGVhdG1hcF9sZWdlbmRfc2lkZSA9ICJyaWdodCIpCmBgYAoKPGRpdiBjbGFzcyA9ICJibHVlLWJveCI+Ci0gVGhlICpjZWxsLXNwZWNpZmljKiBtb2RlbHMgcHJlZGljdGVkIGByIHRvdGFsLnByZWRpY3RlZC5zeW5lcmdpZXMuY2VsbC5zcGVjaWZpYy5udW1gIApvZiB0aGUgYHIgdG90YWwub2JzZXJ2ZWQuc3luZXJnaWVzLm51bWAgb2JzZXJ2ZWQgc3luZXJnaWVzIGZvdW5kIGFjcm9zcyB0aGUgCmByIGxlbmd0aChjZWxsLmxpbmVzKWAgY2VsbCBsaW5lcywgd2hlcmVhcyB0aGUgKnJhbmRvbSogbW9kZWxzIHByZWRpY3RlZCAKYHIgdG90YWwucHJlZGljdGVkLnN5bmVyZ2llcy5yYW5kb20ubnVtYCBvZiB0aGUgdGhlbS4KVGh1cywgdGhlICoqdG90YWwgdHJ1ZSBwb3NpdGl2ZSBjb3ZlcmFnZSBmb3IgYWxsIHRoZSBtb2RlbHMgYWNyb3NzIGFsbCBjZWxsIGxpbmVzIGlzIApgciAobGVuZ3RoKHVuaW9uKHRvdGFsLnByZWRpY3RlZC5zeW5lcmdpZXMuY2VsbC5zcGVjaWZpYywgdG90YWwucHJlZGljdGVkLnN5bmVyZ2llcy5yYW5kb20pKS90b3RhbC5vYnNlcnZlZC5zeW5lcmdpZXMubnVtKSoxMDBgJSoqCi0gTm90ZSB0aGF0IHRoZXJlIGV4aXN0IHN5bmVyZ2llcyB3aGljaCB3ZXJlIG9ic2VydmVkIGluIGFsbCBjZWxsIGxpbmVzIChgQUstQklgLApgUEktRDFgKQotIGBBSy1HNGAgYW5kIGA1Wi1EMWAgYXJlIG9ic2VydmVkIHN5bmVyZ2llcyB0aGF0IG9ubHkgdGhlIGNlbGwtc3BlY2lmaWMgbW9kZWxzIGNvdWxkIHByZWRpY3QsIHdoZXJlYXMgdGhlIGBHMi1QNWAgYW5kIGBQSS1ENGAgYXJlIG9ic2VydmVkIHN5bmVyZ2llcyB0aGF0IG9ubHkgdGhlIHJhbmRvbSBtb2RlbHMgY291bGQgcHJlZGljdC4gVGhpcyBzaG93cyB1cyB0aGF0IGEgKipjb21wbGltZW50YXJ5CmFwcHJvYWNoKiogaXMgbmVlZGVkIHdoZW4gc2VhcmNoaW5nIGZvciBiaW9tYXJrZXJzIGFzIHRoZSB0d28gZGlmZmVyZW50IGtpbmQgb2YgCm1vZGVscyAodHJhaW5lZCB0byBhIHNwZWNpZmljIGFjdGl2aXR5IHN0YXRlIHByb2ZpbGUgdnMgdHJhaW5lZCB0byBwcm9saWZlcmF0aW9uKQphbHRob3VnaCB0aGV5IHNoYXJlIGNvbW1vbiB0cnVlIHBvc2l0aXZlcyByZWdhcmRpbmcgdGhlIHN5bmVyZ2llcyB0aGV5IHByZWRpY3QsCnRoZXJlIGFyZSBhbHNvIHN5bmVyZ2llcyBvbmx5IGEgc3BlY2lmaWMgY2xhc3Mgb2YgbW9kZWxzIGNvdWxkIHByZWRpY3QuCi0gVGhlICQzJCBzeW5lcmdpZXMgb2YgaW50ZXJlc3QsIGBBSy1QRGAsIGBQRC1QSWAgYW5kIGBQRC1HMmAgd2VyZSBvYnNlcnZlZCBpbiAKdGhlIGBBNDk4YCBjZWxsIGxpbmUgYW5kIHByZWRpY3RlZCBieSBib3RoIHRoZSBjZWxsLXNwZWNpZmljIGFuZCByYW5kb20gbW9kZWxzLgo8L2Rpdj4KCiMjIEFLLVBEIGJpb21hcmtlcnMgey19CgpBcyBvYnNlcnZlZCBhYm92ZSwgdGhlIGBBSy1QRGAgc3luZXJneSB3YXMgcHJlZGljdGVkIGJ5IGJvdGggdGhlICoqY2VsbCBzcGVjaWZpYyoqIGFuZCAqKnJhbmRvbSBtb2RlbHMqKiBpbiB0aGUgYEE0OThgIGNlbGwgbGluZS4KCiMjIyBDZWxsLXNwZWNpZmljIChBNDk4KSB7LX0KCldlIGdldCB0aGUgYXZlcmFnZSBzdGF0ZSBhbmQgbGluayBvcGVyYXRvciBkaWZmZXJlbmNlcyBwZXIgbmV0d29yayBub2RlIGZvciB0aGUgYEE0OThgIGNlbGwgbGluZSBmcm9tIHRoZSBjZWxsLXNwZWNpZmljIG1vZGVsczoKCmBgYHtyIEFLLVBEIGRpZmYgKENlbGwgc3BlY2lmaWMgbW9kZWxzIC0gQTQ5OCl9CkFLLlBELmF2Zy5zdGF0ZS5kaWZmLmNlbGwuc3BlY2lmaWMgPSBjZWxsLnNwZWNpZmljLnN5bmVyZ3kuYW5hbHlzaXMucmVzJEE0OTgkZGlmZi5zdGF0ZS5zeW5lcmdpZXMubWF0WyJBSy1QRCIsIF0KQUsuUEQuYXZnLmxpbmsuZGlmZi5jZWxsLnNwZWNpZmljICA9IGNlbGwuc3BlY2lmaWMuc3luZXJneS5hbmFseXNpcy5yZXMkQTQ5OCRkaWZmLmxpbmsuc3luZXJnaWVzLm1hdFsiQUstUEQiLCBdCmBgYAoKV2UgYnVpbGQgdGhlIG5ldHdvcmsgZnJvbSB0aGUgdG9wb2xvZ3kgZmlsZToKCmBgYHtyIE5ldHdvcmsgYnVpbGRpbmd9CnRvcG9sb2d5LmZpbGUgPSBwYXN0ZTAoZ2V0d2QoKSwgIi90b3BvbG9neSIpCm5ldCA9IGNvbnN0cnVjdF9uZXR3b3JrKHRvcG9sb2d5LmZpbGUgPSB0b3BvbG9neS5maWxlLCBtb2RlbHMuZGlyID0gIHBhc3RlMChnZXR3ZCgpLCAiL0FHUy9tb2RlbHMiKSkKCiMgYSBzdGF0aWMgbGF5b3V0IGZvciBwbG90dGluZyB0aGUgc2FtZSBuZXR3b3JrIGFsd2F5cwpjb29yZGluYXRlcy5maWxlID0gcGFzdGUwKGdldHdkKCksICIvbmV0d29ya194eV9jb29yZGluYXRlcyIpCm5pY2UubGF5b3V0ID0gYXMubWF0cml4KHJlYWQudGFibGUoY29vcmRpbmF0ZXMuZmlsZSkpCmBgYAoKIyMjIyBBY3Rpdml0eSBzdGF0ZSBiaW9tYXJrZXJzIHstfQoKV2Ugd2lsbCBub3cgdmlzdWFsaXplIHRoZSBub2RlcyBhdmVyYWdlIHN0YXRlIGRpZmZlcmVuY2VzIGluIGEgbmV0d29yayBncmFwaC4gTm90ZSB0aGF0IHRoZSAqKmdvb2QgbW9kZWxzKiogYXJlIHRob3NlIHRoYXQgcHJlZGljdGVkIHRoZSBgQUstUERgIGRydWcgY29tdGlvbiB0byBiZSAqc3luZXJnaXN0aWMqIGFuZCB3ZXJlIGNvbnRyYXN0ZWQgdG8gdGhvc2UgdGhhdCBwcmVkaWN0ZWQgaXQgdG8gYmUgKmFudGFnb25pc3RpYyogKCoqYmFkIG1vZGVscyoqKS4gVGhlIG51bWJlciBvZiBtb2RlbHMgaW4gZWFjaCBjYXRlZ29yeSB3ZXJlOgoKYGBge3IgQ291bnQgZ29vZCB2cyBiYWQgbW9kZWxzIGZvciBBSy1QRCBzeW5lcmd5IChjZWxsLXNwZWNpZmljIG1vZGVscyksIHJlc3VsdHM9J2FzaXMnfQptb2RlbC5wcmVkaWN0aW9ucyA9IG1vZGVsLnByZWRpY3Rpb25zLnBlci5jZWxsLmxpbmVbWyJBNDk4Il1dCm1vZGVscy5zdGFibGUuc3RhdGUgPSBtb2RlbHMuc3RhYmxlLnN0YXRlLnBlci5jZWxsLmxpbmVbWyJBNDk4Il1dCmRydWcuY29tYiA9ICJBSy1QRCIKCmdvb2QubW9kZWxzLm51bSA9IHN1bShtb2RlbC5wcmVkaWN0aW9uc1ssIGRydWcuY29tYl0gPT0gMSAmICFpcy5uYShtb2RlbC5wcmVkaWN0aW9uc1ssIGRydWcuY29tYl0pKQojIHVuaXF1ZSBnb29kIG1vZGVscwojIG1vZGVscy5saW5rLm9wZXJhdG9yID0gbW9kZWxzLmxpbmsub3BlcmF0b3JzLnBlci5jZWxsLmxpbmUkQTQ5OAojIG5yb3codW5pcXVlKG1vZGVscy5saW5rLm9wZXJhdG9yW21vZGVsLnByZWRpY3Rpb25zWywgZHJ1Zy5jb21iXSA9PSAxICYgIWlzLm5hKG1vZGVsLnByZWRpY3Rpb25zWywgZHJ1Zy5jb21iXSksIF0pKQpiYWQubW9kZWxzLm51bSAgPSBzdW0obW9kZWwucHJlZGljdGlvbnNbLCBkcnVnLmNvbWJdID09IDAgJiAhaXMubmEobW9kZWwucHJlZGljdGlvbnNbLCBkcnVnLmNvbWJdKSkKCnByZXR0eV9wcmludF9zdHJpbmcocGFzdGUwKCJOdW1iZXIgb2YgJ2dvb2QnIG1vZGVscyAoQUstUEQgc3luZXJnaXN0aWMpIGluIHRoZSBBNDk4IGNlbGwgbGluZTogIiwgZ29vZC5tb2RlbHMubnVtKSkKcHJldHR5X3ByaW50X3N0cmluZyhwYXN0ZTAoIk51bWJlciBvZiAnYmFkJyBtb2RlbHMgKEFLLVBEIGFudGFnb25pc3RpYykgaW4gdGhlIEE0OTggY2VsbCBsaW5lOiAiLCBiYWQubW9kZWxzLm51bSkpCmBgYAoKYGBge3IgQUstUEQgYWN0aXZpdHkgc3RhdGUgYmlvbWFya2VycyAoQ2VsbCBzcGVjaWZpYyBtb2RlbHMgLSBBNDk4KSwgY2FjaGUgPSBUUlVFfQpwbG90X2F2Z19zdGF0ZV9kaWZmX2dyYXBoKG5ldCwgZGlmZiA9IEFLLlBELmF2Zy5zdGF0ZS5kaWZmLmNlbGwuc3BlY2lmaWMsIAogIGxheW91dCA9IG5pY2UubGF5b3V0LCB0aXRsZSA9ICJBSy1QRCBhY3Rpdml0eSBzdGF0ZSBiaW9tYXJrZXJzIChDZWxsIHNwZWNpZmljIG1vZGVscyAtIEE0OTgpIikKYGBgCgpUaHVzLCB3ZSBjYW4gaWRlbnRpZnkgdGhlIGFjdGl2ZSBzdGF0ZSBiaW9tYXJrZXJzOgoKYGBge3IgQUstUEQgYWN0aXZlIHN0YXRlIGJpb21hcmtlcnMgKENlbGwgc3BlY2lmaWMgbW9kZWxzIC0gQTQ5OCksIHJlc3VsdHMgPSAnYXNpcyd9CkFLLlBELmFjdGl2ZS5iaW9tYXJrZXJzID0gQUsuUEQuYXZnLnN0YXRlLmRpZmYuY2VsbC5zcGVjaWZpY1tBSy5QRC5hdmcuc3RhdGUuZGlmZi5jZWxsLnNwZWNpZmljID4gMC43XQpwcmV0dHlfcHJpbnRfdmVjdG9yX25hbWVzKEFLLlBELmFjdGl2ZS5iaW9tYXJrZXJzKQpgYGAKClNvLCB0aGUgYEFLLVBEYCBzeW5lcmd5IG1hbmlmZXN0cyBpbiBjYW5jZXIgY2VsbCBtb2RlbHMgdGhhdCBoYXZlIHRoZSBgRVJLX2ZgIGZhbWlseSBsb2dpY2FsIG5vZGUgaW4gYW4gKiphY3RpdmUqKiBzdGF0ZS4KVGhlICoqTUFQSy1FUksgc2lnbmFsaW5nIHBhdGh3YXkqKiBoYXMgYmVlbiBzdHVkaWVkIGEgbG90IGFuZCBoYXMgYmVlbiBmb3VuZCB0byBiZSAqKm92ZXJleHByZXNzZWQvaGF2ZSBpbmNyZWFzZWQgYWN0aXZpdHkqKiBpbiBjYW5jZXJzIGFuZCBhcyBzdWNoIGNhbmNlciB0cmVhdG1lbnRzIHRoYXQgaW5jbHVkZSB0aGUgaW5oaWJpdGlvbiBvZiB0aGF0IHBhdGh3YXkgYXJlIGZvdW5kIHRvIGJlIG1vc3QgYmVuZWZpY2lhbC4KClBhcGVyIGV2aWRlbmNlIGZvciAqKmBFUktgIG92ZXJleHByZXNzaW9uKiogaW4gY2FuY2VyICh0aGVyZSBhcmUgbWFueSk6IAoKLSBbUm9sZXMgb2YgdGhlIFJhZi9NRUsvRVJLIHBhdGh3YXkgaW4gY2VsbCBncm93dGgsIG1hbGlnbmFudCB0cmFuc2Zvcm1hdGlvbiBhbmQgZHJ1ZyByZXNpc3RhbmNlXShodHRwczovL2RvaS5vcmcvMTAuMTAxNi9qLmJiYW1jci4yMDA2LjEwLjAwMSkKLSBbVGhlIFJhcy9SYWYvTUVLL0VSSyBzaWduYWxpbmcgcGF0aHdheSBhbmQgaXRzIHJvbGUgaW4gdGhlIG9jY3VycmVuY2UgYW5kIGRldmVsb3BtZW50IG9mIEhDQyAoUmV2aWV3KV0oaHR0cHM6Ly9kb2kub3JnLzEwLjM4OTIvb2wuMjAxNi41MTEwKQotIFtUYXJnZXRpbmcgRVJLLCBhbiBBY2hpbGxlcycgSGVlbCBvZiB0aGUgTUFQSyBwYXRod2F5LCBpbiBjYW5jZXIgdGhlcmFweV0oaHR0cHM6Ly9kb2kub3JnLzEwLjEwMTYvai5hcHNiLjIwMTguMDEuMDA4KQotIFtFUksgaXMgYSBQaXZvdGFsIFBsYXllciBvZiBDaGVtby1JbW11bmUtUmVzaXN0YW5jZSBpbiBDYW5jZXJdKGh0dHBzOi8vZG9pLm9yZy8xMC4zMzkwL2lqbXMyMDEwMjUwNSkKClRoZSBpbmhpYml0ZWQgc3RhdGUgYmlvbWFya2VycyBhcmU6CgpgYGB7ciBBSy1QRCBpbmhpYml0ZWQgc3RhdGUgYmlvbWFya2VycyAoQ2VsbCBzcGVjaWZpYyBtb2RlbHMgLSBBNDk4KSwgcmVzdWx0cyA9ICdhc2lzJ30KQUsuUEQuaW5oaWJpdGVkLmJpb21hcmtlcnMgPSBBSy5QRC5hdmcuc3RhdGUuZGlmZi5jZWxsLnNwZWNpZmljW0FLLlBELmF2Zy5zdGF0ZS5kaWZmLmNlbGwuc3BlY2lmaWMgPCAtMC43XQpwcmV0dHlfcHJpbnRfdmVjdG9yX25hbWVzKEFLLlBELmluaGliaXRlZC5iaW9tYXJrZXJzKQpgYGAKCjxkaXYgY2xhc3M9ImdyZWVuLWJveCI+CldlIHdpbGwgZGVtb25zdHJhdGUgdGhhdCB0aGUgYFBUUE4xMWAgYW5kIGBHQUJfZmAgaW5oaWJpdGVkIGJpb21hcmtlcnMgYXJlIGEgZGlyZWN0IGNvbnNlcXVlbmNlIG9mIHRoZSBvdmVyZXByZXNzaW9uIG9mIGBFUktfZmA6CjwvZGl2Pgo8L2JyPgoKSWYgd2UgY2hlY2sgdGhlIGxvZ2ljYWwgZXF1YXRpb25zIHJlbGF0ZWQgdG8gdGhlIGFib3ZlIGJpb21hcmtlcnMgd2Ugc2VlIHRoYXQ6CgpgYGB7ciBwcmludCBlcXVhdGlvbnMsIHJlc3VsdHM9J2FzaXMnfQpwcmV0dHlfcHJpbnRfc3RyaW5nKCJFUktfZiAqPSAgKCAgTUVLX2YgKSBBTkQvT1IgTk9UICAoICAoIERVU1A2ICkgIG9yIFBQUDFDQSApIikKcHJldHR5X3ByaW50X3N0cmluZygiR0FCX2YgKj0gICggIEdSQjIgKSBBTkQvT1IgTk9UICggRVJLX2YgKSIpCnByZXR0eV9wcmludF9zdHJpbmcoIlBUUE4xMSAqPSAgKCAgR0FCX2YgKSIpCmBgYAoKU28sIHByZXR0eSBtdWNoIGlmIHRoZSBgR0FCX2ZgIG5vZGUgaXMgbW9yZSBpbmhpYml0ZWQgaW4gdGhlIG1vZGVscyB0aGF0IHByZWRpY3RlZCB0aGUgYEFLLVBEYCBzeW5lcmd5IChnb29kIG1vZGVscyksIHRoZW4gYFBUUE4xMWAgaXMgYWxzbyBhcyB3ZWxsLiAKQWxzbyB0aGUgYXZlcmFnZSBhY3Rpdml0eSBkaWZmZXJlbmNlIG9mIHRoZSBgR1JCMmAgbm9kZSBpcyBgciBBSy5QRC5hdmcuc3RhdGUuZGlmZi5jZWxsLnNwZWNpZmljWyJHUkIyIl1gLCB3aGljaCBtYWtlcyB0aGUgYEdBQl9mYCBub2RlIG1vcmUgaW5oaWJpdGVkIGluIHRoZSBnb29kIG1vZGVscyBzaW5jZSBpdCdzIGFjdGl2aXR5IGlzIG1vc3RseSBkZXBlbmRlbnQgb24gdGhlIGBFUktfZmAgbm9kZSwgd2hpY2ggaXMgbW9zdGx5IG92ZXJleHByZXNzZWQgaW4gdGhlIGdvb2QgbW9kZWxzLgpBbGwgaW4gYWxsLCB0aGUgb3ZlcmV4cHJlc3Npb24gb2YgYEVSS19mYCBpcyB3aGF0IGNhdXNlcyB0aGUgdHdvIG90aGVyIGluaGliaXRlZCBiaW9tYXJrZXJzLgoKPC9icj4KCiMjIyMgTGluayBvcGVyYXRvciBiaW9tYXJrZXJzIHstfQoKV2UgdmlzdWFsaXplIHRoZSBub2RlcyBhdmVyYWdlIGxpbmsgb3BlcmF0b3IgZGlmZmVyZW5jZXMgaW4gYSBuZXR3b3JrIGdyYXBoOgoKYGBge3IgQUstUEQgbGluayBvcGVyYXRvciBiaW9tYXJrZXJzIChDZWxsIHNwZWNpZmljIG1vZGVscyAtIEE0OTgpLCBjYWNoZSA9IFRSVUV9CnBsb3RfYXZnX2xpbmtfb3BlcmF0b3JfZGlmZl9ncmFwaChuZXQsIGRpZmYgPSBBSy5QRC5hdmcubGluay5kaWZmLmNlbGwuc3BlY2lmaWMsIAogIGxheW91dCA9IG5pY2UubGF5b3V0LCB0aXRsZSA9ICJBSy1QRCBsaW5rIG9wZXJhdG9yIGJpb21hcmtlcnMgKENlbGwgc3BlY2lmaWMgbW9kZWxzIC0gQTQ5OCkiKQpgYGAKClNvLCB0aGUgbW9kZWxzIHRoYXQgcHJlZGljdGVkIHRoZSBgQUstUERgIHN5bmVyZ3ksIGhhZCB0aGUgKipPUiBOT1QqKiBhcyBhIGxpbmsgb3BlcmF0b3IgaW4gdGhlIGJvb2xlYW4gZXF1YXRpb24gdGhhdCBoYXMgdGhlIGBFUktfZmAgbm9kZSBhcyB0YXJnZXQsIGkuZS4gYEVSS19mICo9ICAoICBNRUtfZiApIE9SIE5PVCAgKCAgKCBEVVNQNiApICBvciBQUFAxQ0EgKWAgaW5zdGVhZCBvZiBgRVJLX2YgKj0gICggIE1FS19mICkgQU5EIE5PVCAoICggRFVTUDYgKSAgb3IgUFBQMUNBIClgLiAKVGhlIGRpZmZlcmVuY2UgaW4gdGhlIHJlc3VsdCBvZiB0aGUgbG9naWNhbCBlcXVhdGlvbiBjYW4gYmUgc2VlbiBpbiB0aGUgbmV4dCB0d28gdHJ1dGggdGFibGVzIHdoZXJlIHRoZSB1c2Ugb2YgdGhlICoqT1IgTk9UIGxpbmsgb3BlcmF0b3IqKiBtYWtlcyB0aGUgZW5kIHRydXRoIHZhbHVlIG1vcmUgKmZsZXhpYmxlKiBpbiB0aGUgc2Vuc2UgdGhhdCBhIGxvdCBtb3JlIG1vcmUgVFJVRSB2YWx1ZXMgYXJlIHBvc3NpYmxlLCBzaW5jZSB0aGUgYWN0aXZhdGluZyByZWd1bGF0b3IgYE1FS19mYCAod2hpY2ggaXMgdGhlIGBQRGAgZHJ1ZydzIHRhcmdldCkgaGFzIG1vcmUgd2VpZ2h0IGluIHRoZSBvdXRjb21lOgoKIVtdKGltZy90cnV0aF90YWJsZV9hbmQuc3ZnKSFbXShpbWcvdHJ1dGhfdGFibGVfb3Iuc3ZnKQoKPGRpdiBjbGFzcz0ib3JhbmdlLWJveCI+CklmIHlvdSB0aGluayBpdCBpbiByZXZlcnNlIHRlcm1zLCBpbiBjYW5jZXIgbW9kZWxzIGluIHdoaWNoIHRoZSBgRVJLX2ZgIGVxdWF0aW9uIGlzIG1vc3RseSBiYXNlZCBvbiBhbiBgQU5EIE5PVGAgbGluayBvcGVyYXRvciBsb2dpYyBhbmQgaGF2ZSB0aGUgYEVSS19GYCBub2RlIGFzICppbmFjdGl2ZSogKGFsc28gZnJvbSB0aGUgZXF1YXRpb24gYERVU1A2ICo9ICAoICAoICBtVE9SQzFfYyApICBvciBFUktfZiApYCwgYERVU1A2YCB3aWxsIGFsc28gYmUgaW5hY3RpdmUpLCB0aGUgYE1FS19mYCBub2RlIHdpbGwgYWxzbyBiZSBtb3N0bHkgKippbmFjdGl2ZSoqICgkMiQgb3V0IG9mICQzJCBjYXNlcyBpbiB0aGUgdGFibGUgYWJvdmU6IDFzdCwgMm5kIGFuZCA0dGggcm93KSAtIGl0IGNhbiBiZSBzZWVuIGFzIGEgbm9kZSB0aGF0IGRvZXMgbm90IHBsYXkgYW4gaW1wb3J0YW50IHJvbGUgaW4gdGhlIGFjdGl2aXR5IG91dGNvbWUgb2YgKipFUktfZioqIC0gYW5kIHNvIGJ5IGluaGliaXRpbmcgaXQgZnVydGhlciB3aXRoIHRoZSBgUERgIGRydWcgd2lsbCBub3QgYnJpbmcgYW55IHNpZ25pZmljYW50IGJlbmVmaXQgZm9yIGNhbmNlciB0cmVhdG1lbnQuCjwvZGl2Pgo8L2JyPgoKPGRpdiBjbGFzcyA9ICJibHVlLWJveCI+ClRodXMsIGluIGNhbmNlciBib29sZWFuIG1vZGVscyB3aGVyZSB0aGUgYEVSS19mYCBub2RlIGlzIG92ZXJleHByZXNzZWQgYW5kIHRoZSBgTUVLX2ZgIGxvZ2ljYWwgbm9kZSBpcyBpdCdzIG1vc3QgY3J1Y2lhbCByZWd1bGF0b3IsIGluaGliaXRpbmcgYm90aCB0aGUgKipNQVBLL0VSSyBwYXRod2F5KiogKGRydWcgYFBEYCkgYW5kIHRoZSAqKkFLVCBwYXRod2F5KiogKGRydWcgYEFLYCkgaXMgYSBzeW5lcmdpc3RpYyBjb21iaW5hdGlvbiBmb3IgY2FuY2VyIHRyZWF0bWVudC4KPC9kaXY+CjwvYnI+CgojIyMjIFN5bmVyZ3kgc3Vic2V0cyBhbmFseXNpcyB7LX0KCkl0IHdpbGwgYmUgaW50ZXJlc3RpbmcgdG8gZmluZCAqKmFsbCB0aGUgcG9zc2libGUgc3luZXJneSBzZXRzIGFuZCBzdWJzZXRzIHRoYXQgaW5jbHVkZSB0aGUgYEFLLVBEYCBhcyB0aGUgZXh0cmEgc3luZXJneSoqLiAKVXNpbmcgdGhlc2Ugc3luZXJneSBzZXRzLCBtb2RlbHMgdGhhdCBwcmVkaWN0IGEgc2V0IG9mIHN5bmVyZ2llcyB3aWxsIGJlIGNvbnRyYXN0ZWQgdG8gbW9kZWxzIHRoYXQgcHJlZGljdGVkIHRoZSBzYW1lIHNldCB3aXRoIHRoZSBhZGRpdGlvbiBvZiB0aGUgZXh0cmEgYEFLLVBEYCBzeW5lcmd5LiBUaHVzIHdlIGNvdWxkIGZpbmQgKipzeW5lcmd5IGJpb21hcmtlcnMgdGhhdCBhbGxvdyBhbHJlYWR5IGdvb2QgcHJlZGljdGluZyBtb2RlbHMgdG8gcHJlZGljdCB0aGUgYWRkaXRpb25hbCBzeW5lcmd5IG9mIGludGVyZXN0KiouClRoaXMgaW52ZXN0aWdhdGlvbiB3aWxsIGFsbG93IHVzIHRodXMgdG8gcmVmaW5lIHRoZSBhY3Rpdml0eSBzdGF0ZSBhbmQgbGluayBvcGVyYXRvciBiaW9tYXJrZXJzIHdlIGZvdW5kIGFib3ZlLgoKV2UgZmlyc3QgY29uc3RydWN0IHR3byBtYXRyaWNlczogaW4gdGhlIGZpcnN0LCAqKmVhY2ggcm93IGlzIGEgc2V0IHZzIHN1YnNldCBhdmVyYWdlIGFjdGl2aXR5IGRpZmZlcmVuY2UgdmVjdG9yIG9mIHRoZSBuZXR3b3JrIG5vZGVzKiosIHdoaWxlIG9uIHRoZSBzZWNvbmQgKiplYWNoIHJvdyBpcyBhIHNldCB2cyBzdWJzZXQgYXZlcmFnZSBsaW5rIG9wZXJhdG9yIGRpZmZlcmVuY2UgdmVjdG9yIG9mIHRoZSBuZXR3b3JrIG5vZGVzKio6CmBgYHtyIFN5bmVyZ3kgU3Vic2V0cyBDb21wYXJpc29uIChDZWxsLXNwZWNpZmljIC0gQTQ5OCksIGNhY2hlPVRSVUV9Cm1vZGVsLnByZWRpY3Rpb25zID0gbW9kZWwucHJlZGljdGlvbnMucGVyLmNlbGwubGluZSRBNDk4Cm1vZGVscy5zdGFibGUuc3RhdGUgPSBtb2RlbHMuc3RhYmxlLnN0YXRlLnBlci5jZWxsLmxpbmUkQTQ5OAptb2RlbHMubGluay5vcGVyYXRvciA9IG1vZGVscy5saW5rLm9wZXJhdG9ycy5wZXIuY2VsbC5saW5lJEE0OTgKICAKcmVzID0gZ2V0X3N5bmVyZ3lfY29tcGFyaXNvbl9zZXRzKGNlbGwuc3BlY2lmaWMuc3luZXJneS5hbmFseXNpcy5yZXMkQTQ5OCRzeW5lcmd5LnN1YnNldC5zdGF0cykKQUsuUEQucmVzID0gcmVzICU+JSBmaWx0ZXIoc3luZXJnaWVzID09ICJBSy1QRCIpCgpkaWZmLnN0YXRlLmxpc3QgPSBsaXN0KCkKZGlmZi5saW5rLmxpc3QgPSBsaXN0KCkKZm9yIChpIGluIDE6bnJvdyhBSy5QRC5yZXMpKSB7CiAgc3luZXJneS5zZXQgICAgPSBBSy5QRC5yZXNbaSwgMl0KICBzeW5lcmd5LnN1YnNldCA9IEFLLlBELnJlc1tpLCAzXQogIAogIHN5bmVyZ3kuc2V0LnN0ciAgICA9IHVubGlzdChzdHJzcGxpdCh4ID0gc3luZXJneS5zZXQsIHNwbGl0ID0gIiwiKSkKICBzeW5lcmd5LnN1YnNldC5zdHIgPSB1bmxpc3Qoc3Ryc3BsaXQoeCA9IHN5bmVyZ3kuc3Vic2V0LCBzcGxpdCA9ICIsIikpCiAgCiAgIyBjb3VudCBtb2RlbHMKICBzeW5lcmd5LnNldC5tb2RlbHMubnVtID0gY291bnRfbW9kZWxzX3RoYXRfcHJlZGljdF9zeW5lcmdpZXMoc3luZXJneS5zZXQuc3RyLCBtb2RlbC5wcmVkaWN0aW9ucykKICBzeW5lcmd5LnN1YnNldC5tb2RlbHMubnVtID0gY291bnRfbW9kZWxzX3RoYXRfcHJlZGljdF9zeW5lcmdpZXMoc3luZXJneS5zdWJzZXQuc3RyLCBtb2RlbC5wcmVkaWN0aW9ucykKICAKICAjIGlmIHRvbyBzbWFsbCBudW1iZXIgb2YgbW9kZWxzLCBza2lwIHRoZSBkaWZmIHZlY3RvcgogIGlmICgoc3luZXJneS5zZXQubW9kZWxzLm51bSA8PSAzKSB8IChzeW5lcmd5LnNldC5tb2RlbHMubnVtIDw9IDMpKSAKICAgIG5leHQKICAKICAjIGdldCB0aGUgZGlmZgogIGRpZmYuc3RhdGUuYWsucGQgPSBnZXRfYXZnX2FjdGl2aXR5X2RpZmZfYmFzZWRfb25fc3luZXJneV9zZXRfY21wKHN5bmVyZ3kuc2V0LnN0ciwgc3luZXJneS5zdWJzZXQuc3RyLCBtb2RlbC5wcmVkaWN0aW9ucywgbW9kZWxzLnN0YWJsZS5zdGF0ZSkKICBkaWZmLmxpbmsuYWsucGQgPSBnZXRfYXZnX2xpbmtfb3BlcmF0b3JfZGlmZl9iYXNlZF9vbl9zeW5lcmd5X3NldF9jbXAoc3luZXJneS5zZXQuc3RyLCBzeW5lcmd5LnN1YnNldC5zdHIsIG1vZGVsLnByZWRpY3Rpb25zLCBtb2RlbHMubGluay5vcGVyYXRvcikKICBkaWZmLnN0YXRlLmxpc3RbW3Bhc3RlMChzeW5lcmd5LnNldCwgIiB2cyAiLCBzeW5lcmd5LnN1YnNldCldXSA9IGRpZmYuc3RhdGUuYWsucGQKICBkaWZmLmxpbmsubGlzdFtbcGFzdGUwKHN5bmVyZ3kuc2V0LCAiIHZzICIsIHN5bmVyZ3kuc3Vic2V0KV1dID0gZGlmZi5saW5rLmFrLnBkCn0KCmRpZmYuc3RhdGUubWF0ID0gZG8uY2FsbChyYmluZCwgZGlmZi5zdGF0ZS5saXN0KQpkaWZmLmxpbmsubWF0ID0gZG8uY2FsbChyYmluZCwgZGlmZi5saW5rLmxpc3QpCgpjYXB0aW9uLnRpdGxlLjEgPSAiVGFibGUgMTogQXZlcmFnZSBhY3Rpdml0eSBkaWZmZXJlbmNlIHZhbHVlcyBhY3Jvc3MgYWxsIHN5bmVyZ3kgc2V0IGNvbXBhcmlzb25zIChBSy1QRCkiCmRhdGF0YWJsZShkYXRhID0gZGlmZi5zdGF0ZS5tYXRbLCBjKCJTUkMiLCAiQ1NLIiwgIk1FS19mIiwgIlNUQVQxIiwgIlBUUE42IildLCBvcHRpb25zID0gbGlzdCgKICAgIHNlYXJjaGluZyA9IEZBTFNFLCBwYWdlTGVuZ3RoID0gNSwgbGVuZ3RoTWVudSA9IGMoNSwgMTApKSwKICBjYXB0aW9uID0gaHRtbHRvb2xzOjp0YWdzJGNhcHRpb24oY2FwdGlvbi50aXRsZS4xLCBzdHlsZT0iY29sb3I6I2RkNDgxNDsgZm9udC1zaXplOiAxOHB4IikpICU+JSAKICBmb3JtYXRSb3VuZCgxOm5jb2woZGlmZi5zdGF0ZS5tYXQpLCBkaWdpdHMgPSAzKQoKY2FwdGlvbi50aXRsZS4yID0gIlRhYmxlIDI6IEF2ZXJhZ2UgbGluayBvcGVyYXRvciBkaWZmZXJlbmNlIHZhbHVlcyBhY3Jvc3MgYWxsIHN5bmVyZ3kgc2V0IGNvbXBhcmlzb25zIChBSy1QRCkiCmRhdGF0YWJsZShkYXRhID0gZGlmZi5saW5rLm1hdFssIGMoIlNSQyIsICJSQUNfZiIsICJNRUtfZiIsICJTVEFUMSIsICJQVEVOIildLCBvcHRpb25zID0gbGlzdCgKICAgIHNlYXJjaGluZyA9IEZBTFNFLCBwYWdlTGVuZ3RoID0gNSwgbGVuZ3RoTWVudSA9IGMoNSwgMTApKSwKICBjYXB0aW9uID0gaHRtbHRvb2xzOjp0YWdzJGNhcHRpb24oY2FwdGlvbi50aXRsZS4yLCBzdHlsZT0iY29sb3I6I2RkNDgxNDsgZm9udC1zaXplOiAxOHB4IikpICU+JSAKICBmb3JtYXRSb3VuZCgxOm5jb2woZGlmZi5saW5rLm1hdCksIGRpZ2l0cyA9IDMpCmBgYAoKVXNpbmcgdGhlIG1hdHJpeCBmcm9tIFRhYmxlIDEgKHdoZXJlIHdlIHNob3cganVzdCAkNSQgbm9kZXMpLCB3ZSBjb3VudCBwZXIgbmV0d29yayBub2RlICoqdGhlIG51bWJlciBvZiB0aW1lcyB0aGF0IHRoZSBub2RlJ3MgYXZlcmFnZSBhY3Rpdml0eSBkaWZmZXJlbmNlIHZhbHVlIGhhcyBzdXJwYXNzZWQgYSBzcGVjaWZpZWQgdGhyZXNob2xkKiogLSBpLmUuIHRoZSBudW1iZXIgb2YgdGltZXMgaXQgaGFzIGJlZW4gZm91bmQgYXMgaW1wb3J0YW50IChhIGJpb21hcmtlcikgYWNyb3NzIGFsbCB0aGUgc3luZXJneSBzZXQgY29tcGFyaXNvbnMgKHNvIHRoZSBtb3JlIHRoZSBiZXR0ZXIpOgpgYGB7ciBBY3Rpdml0eSBTdGF0ZSBCaW9tYXJrZXIgY29udGluZ2VuY3kgdGFibGUgYWNyb3NzIGFsbCBjb21wYXJpc29uIHNldHMgKEFLLVBEIC0gQ2VsbC1zcGVjaWZpYyksIHJlc3VsdHM9J2FzaXMnfQp0aHJlc2hvbGQgPSAwLjgKYmlvbWFya2VyLnN0YXRlLm1hdCA9IGJpbmFyaXplX3RvX3RocmVzKG1hdCA9IGRpZmYuc3RhdGUubWF0LCB0aHJlc2hvbGQpCmJpb21hcmtlci5zdGF0ZS5jb3VudHMgPSBjb2xTdW1zKGJpb21hcmtlci5zdGF0ZS5tYXQpCnByZXR0eV9wcmludF92ZWN0b3JfbmFtZXNfYW5kX3ZhbHVlcyh0YWJsZShiaW9tYXJrZXIuc3RhdGUuY291bnRzKSkKYGBgCgpUaGUgYWJvdmUgbWVhbnMgdGhhdCB0aGVyZSB3ZXJlICQxNDEkIG5vZGVzIHRoYXQgd2VyZSAqemVybyogdGltZXMgZm91bmQgYXMgKiphY3Rpdml0eSBzdGF0ZSBiaW9tYXJrZXJzKiogYWNyb3NzIGFsbCBzeW5lcmd5IHNldCBjb21wYXJpc29ucywgKipvbmUgdGhhdCB3YXMgZm91bmQgb25jZSBhbmQgJDIkIHRoYXQgd2VyZSBmb3VuZCAkMTEkIHRpbWVzIChvdXQgb2YgYSB0b3RhbCBvZiBgciBucm93KGJpb21hcmtlci5zdGF0ZS5tYXQpYCkqKi4gVGhlc2Ugbm9kZXMgYXJlOgoKYGBge3IgRXh0cmEgYWN0aXZpdHkgc3RhdGUgYmlvbWFya2VycyAoQ2VsbC1zcGVjaWZpYyAtIEFLLVBEKSwgcmVzdWx0cz0nYXNpcyd9CnByZXR0eV9wcmludF92ZWN0b3JfbmFtZXMoYmlvbWFya2VyLnN0YXRlLmNvdW50c1tiaW9tYXJrZXIuc3RhdGUuY291bnRzID09IDExXSkKYGBgCgpJZiB3ZSB2aXN1YWxpemUgdGhlICoqYXZlcmFnZSBhY3Rpdml0eSBzdGF0ZSBkaWZmZXJlbmNlIGFjcm9zcyBhbGwgc3luZXJneSBjb21wYXJpc29uIHNldHMqKiBmcm9tIFRhYmxlIDEgd2UgY2FuIHNlZSB0aGF0IHRoZSBwcmV2aW91cyAkMiQgbm9kZXMgYXJlIG1vcmUgcHJvbm91bmNlZDoKCmBgYHtyIEF2ZXJhZ2UgYWN0aXZpdHkgc3RhdGUgZGlmZiBhY3Jvc3MgYWxsIHN5bmVyZ3kgc3Vic2V0cyAoQUstUEQpLCBjYWNoZT1UUlVFfQpwbG90X2F2Z19zdGF0ZV9kaWZmX2dyYXBoKG5ldCwgZGlmZiA9IGNvbE1lYW5zKGRpZmYuc3RhdGUubWF0KSwgbGF5b3V0ID0gbmljZS5sYXlvdXQsIAogIHRpdGxlID0gIkF2ZXJhZ2UgYWN0aXZpdHkgc3RhdGUgZGlmZiBhY3Jvc3MgYWxsIHN5bmVyZ3kgc3Vic2V0cyAoQUstUEQpIikKYGBgCgpTYW1lIGNhbiBiZSBzZWVuIHdpdGggYSBgZG90Y2hhcnRgIHdoZXJlIHdlIGhhdmUgZXhjbHVkZWQgdGhlIG5vZGVzIHRoYXQgaGF2ZSAqemVybyogYXZlcmFnZSBhY3Rpdml0eSBkaWZmZXJlbmNlOgpgYGB7ciBBdmVyYWdlIGFjdGl2aXR5IHN0YXRlIGRpZmYgYWNyb3NzIGFsbCBzeW5lcmd5IHN1YnNldHMgKEFLLVBEKSAtIGRvdGNoYXJ0LCBjYWNoZT1UUlVFfQpkZiA9IGFzLmRhdGEuZnJhbWUodChhcy5kYXRhLmZyYW1lKGFzLmxpc3QoZGlmZi5zdGF0ZS5hay5wZCkpKSkKY29sbmFtZXMoZGYpID0gImF2Zy5zdGF0ZSIKZGYgPSBkZiAlPiUgCiAgcm93bmFtZXNfdG9fY29sdW1uKHZhciA9ICJub2RlcyIpICU+JQogIGZpbHRlcihhdmcuc3RhdGUgIT0gMCkKCmdnZG90Y2hhcnQoZGYsIHggPSAibm9kZXMiLCB5ID0gImF2Zy5zdGF0ZSIsCiAgdGl0bGUgPSAiQXZlcmFnZSBhY3Rpdml0eSBzdGF0ZSBkaWZmZXJlbmNlIGFjcm9zcyBhbGwgc3luZXJneSBjb21wYXJpc29uIHNldHMgKEFLLVBELCBBNDk4IGNlbGwgbGluZSkiLCAKICBsYWJlbCA9ICJub2RlcyIsIGZvbnQubGFiZWwgPSBsaXN0KHNpemUgPSAxMSwgY29sb3IgPSAiYmx1ZSIpLAogIGxhYmVsLnNlbGVjdCA9IGxpc3QoY3JpdGVyaWEgPSAiYHlgID49IDAuNyB8IGB5YCA8PSAtMC43IiksIAogIHJlcGVsID0gVFJVRSwgbGFiZWwucmVjdGFuZ2xlID0gVFJVRSwKICB4bGFiID0gIk5vZGVzIiwgeWxhYiA9ICJBdmVyYWdlIEFjdGl2aXR5IFN0YXRlIiwgCiAgeWxpbSA9IGMoLTEsMSksIGFkZCA9ICJzZWdtZW50cyIpICsgCiAgICBmb250KCJ4LnRleHQiLCBzaXplID0gMTApICsKICAgIGZvbnQoInRpdGxlIiwgc2l6ZSA9IDExKSArIAogICAgZ2VvbV9obGluZSh5aW50ZXJjZXB0ID0gLTAuNywgbGluZXR5cGUgPSAiZGFzaGVkIiwgY29sb3IgPSAicmVkIikgKyAKICAgIGdlb21faGxpbmUoYWVzKHlpbnRlcmNlcHQgPSAwLjcsIGxpbmV0eXBlID0gIjAuNyIpLCBjb2xvciA9ICJyZWQiKSArCiAgICBzY2FsZV9saW5ldHlwZV9tYW51YWwobmFtZSA9ICJUaHJlc2hvbGQiLCB2YWx1ZXMgPSAyLCAKICAgICAgZ3VpZGUgPSBndWlkZV9sZWdlbmQob3ZlcnJpZGUuYWVzID0gbGlzdChjb2xvciA9ICJyZWQiKSkpICsgCiAgICB0aGVtZShsZWdlbmQucG9zaXRpb24gPSBjKDAuMiwwLjcpKQpgYGAKCjxkaXYgY2xhc3M9Im9yYW5nZS1ib3giPgpOb3RlIHRoZSBib29sZWFuIGVxdWF0aW9uOiBgUFRQTjYgKj0gU1JDYC4gVGhpcyBtZWFucyB0aGF0IGBTUkNgIGlzICoqdGhlIG9ubHkgaW5oaWJpdGVkIG5vZGUgb2YgaW50ZXJlc3QqKgo8L2Rpdj4KPC9icj4KCldlIHdpbGwgbm93IGNoZWNrIGlmIHRoZSBhYm92ZSBvYnNlcnZhdGlvbnMgcmVnYXJkaW5nIHRoZSBhY3Rpdml0eSBvZiB0aGUgbm9kZXMgYXJlIHRydWUgdXNpbmcgdGhlIFtNQ0xQIGRhdGFzZXRdKGh0dHBzOi8vdGNwYXBvcnRhbC5vcmcvbWNscC8jL2Rvd25sb2FkKSBhcyBhIHJlZmVyZW5jZToKCmBgYHtyIE1DTFAgRGF0YXNldCwgY2FjaGU9VFJVRX0KbWNscC5kYXRhID0gcmVhZC50YWJsZShmaWxlID0gIk1DTFAtdjEuMS1MZXZlbDQudHN2IiwgaGVhZGVyID0gVFJVRSwgc3RyaW5nc0FzRmFjdG9ycyA9IEZBTFNFKQpjZWxsLmxpbmVzLmluLm1jbHAgPSBjKCJBNDk4IiwgIkFHUyIsICJEVTE0NSIsICJDT0xPMjA1IiwgIlNXNjIwIiwgIlNGMjk1IiwgIlVBQ0M2MiIsICJNREFNQjQ2OCIpCmBgYAoKV2UgY2hlY2sgdGhlIHBob3NwaG9yeWxhdGlvbiB2YWx1ZSBvZiB0aGUgYFNSQ19wWTUyN2AgYWNyb3NzIGFsbCBjZWxsIGxpbmVzOgpgYGB7ciBNQ0xQIERhdGEgVmlzdWFsaXphdGlvbiAoU1JDKSwgY2FjaGU9VFJVRX0Kc3JjLmRhdGEgPSBtY2xwLmRhdGEgJT4lIHNlbGVjdChTYW1wbGVfTmFtZSwgU1JDX3BZNTI3KSAlPiUgbmEub21pdCgpCgojIGZpbmQgdGhlIGFjdGl2aXR5IGNsYXNzZXMgKDMgY2xhc3NlczogbG93IGFjdGl2aXR5LCBubyBhY3Rpdml0eSwgaGlnaCBhY3Rpdml0eSkKcmVzID0gQ2ttZWFucy4xZC5kcCh4ID0gc3JjLmRhdGEkU1JDLCBrID0gMykKYWN0aXZpdHkgPSBhcy5mYWN0b3IocmVzJGNsdXN0ZXIpCmxldmVscyhhY3Rpdml0eSkgPSBjKCJsb3ciLCAibWVkaXVtIiwgImhpZ2giKQpzcmMuZGF0YSA9IGNiaW5kKHNyYy5kYXRhLCBhY3Rpdml0eSkKCmdnZG90Y2hhcnQoZGF0YSA9IHNyYy5kYXRhLCB4ID0gIlNhbXBsZV9OYW1lIiwgeSA9ICJTUkNfcFk1MjciLCAKICB0aXRsZSA9ICJTUkNfcFk1Mjcgc2lnbmFsaW5nIGFjcm9zcyBhbGwgY2VsbCBsaW5lcyBpbiBNQ0xQIERhdGFzZXQiLAogIGNvbG9yID0gImFjdGl2aXR5IiwgcGFsZXR0ZSA9IGMoInJlZCIsICJncmV5IiwgImdyZWVuIiksIAogIGxhYmVsID0gIlNhbXBsZV9OYW1lIiwgbGFiZWwuc2VsZWN0ID0gY2VsbC5saW5lcy5pbi5tY2xwLCByZXBlbCA9IFRSVUUsCiAgYWRkID0gInNlZ21lbnRzIiwgbGFiZWwucmVjdGFuZ2xlID0gVFJVRSwKICB4bGFiID0gIkNlbGwgTGluZXMiLCB5bGFiID0gIlNSQ19wWTUyNyBTaWduYWxpbmciLAogIGFkZC5wYXJhbXMgPSBsaXN0KGNvbG9yID0gImFjdGl2aXR5IiwgcGFsZXR0ZSA9IGMoInJlZCIsICJncmV5IiwgImdyZWVuIikpKSArIAogICAgdGhlbWUoYXhpcy50ZXh0LnggPSBlbGVtZW50X2JsYW5rKCksIGF4aXMudGlja3MgPSBlbGVtZW50X2JsYW5rKCkpCmBgYAoKPGRpdiBjbGFzcz0iYmx1ZS1ib3giPgpJdCBoYXMgYmVlbiBzaG93biBpbiB2YXJpb3VzIHN0dWRpZXMgKGUuZy4gaW4gW3RoaXMgcGFwZXJdaHR0cHM6Ly9qb3VybmFscy5wbG9zLm9yZy9wbG9zb25lL2FydGljbGU/aWQ9MTAuMTM3MS9qb3VybmFsLnBvbmUuMDA3MTAzNSkpIHRoYXQgdGhlIHBob3NwaG9yeWxhdGlvbiBzaXRlcyBvZiBgU1JDYCBpbmNsdWRlIHRoZSAqKmluaGliaXRpbmcgcGhvc3Bob3R5cm9zaW5lIDUyNyBzaXRlKiosIHdoaWNoIG92ZXJyaWRlcyB0aGUgYFk0MTZgIHBob3NwaG9yeWxhdGlvbiAod2hpY2ggaXMgYWxzbyB0ZXN0ZWQgaW4gdGhlIE1DTFAgZGF0YXNldCkuIApTaW5jZSBmb3IgYEE0OThgIGNlbGwgbGluZSB3ZSBvYnNlcnZlIG9uZSBvZiB0aGUgbGFyZ2VzdCBzaWduYWxpbmcgbWVhc3VyZW1lbnRzIGNvbXBhcmVkIHRvIGFsbCB0aGUgY2VsbCBsaW5lcyBmb3IgdGhlIGBTUkNfcFk1MjdgIHNpdGUsIHRoaXMgbWVhbnMgdGhhdCBgU1JDYCBzaG91bGQgYmUgaW4gYW4gKippbmhpYml0aW5nIHN0YXRlKiosIHdoaWNoIGlzIGV4YWN0bHkgd2hhdCB3ZSBmb3VuZCBmcm9tIG91ciBhbmFseXNpcyBhYm92ZS4KPC9kaXY+CjwvYnI+CgpXZSBhbHNvIGNoZWNrIGZvciB0aGUgcGhvc3Bob3J5bGF0aW9uIHZhbHVlIG9mIHRoZSBgTUFQS19wVDIwMlkyMDRgIChmb3IgdGhlIGBFUktfZmAgYWN0aXZhdGlvbikgYWNyb3NzIGFsbCBjZWxsIGxpbmVzOgpgYGB7ciBNQ0xQIERhdGEgVmlzdWFsaXphdGlvbiAoRVJLX2YpLCBjYWNoZT1UUlVFfQplcmsuZGF0YSA9IG1jbHAuZGF0YSAlPiUgc2VsZWN0KFNhbXBsZV9OYW1lLCBNQVBLX3BUMjAyWTIwNCkgJT4lIG5hLm9taXQoKQoKIyBmaW5kIHRoZSBhY3Rpdml0eSBjbGFzc2VzICgzIGNsYXNzZXM6IGxvdyBleHByZXNzaW9uLCBubyBleHByZXNzaW9uLCBoaWdoIGV4cHJlc3Npb24pCnJlcyA9IENrbWVhbnMuMWQuZHAoeCA9IGVyay5kYXRhJE1BUEtfcFQyMDJZMjA0LCBrID0gMykKYWN0aXZpdHkgPSBhcy5mYWN0b3IocmVzJGNsdXN0ZXIpCmxldmVscyhhY3Rpdml0eSkgPSBjKCJsb3ciLCAibWVkaXVtIiwgImhpZ2giKQplcmsuZGF0YSA9IGNiaW5kKGVyay5kYXRhLCBhY3Rpdml0eSkKCmdnZG90Y2hhcnQoZGF0YSA9IGVyay5kYXRhLCB4ID0gIlNhbXBsZV9OYW1lIiwgeSA9ICJNQVBLX3BUMjAyWTIwNCIsIAogIHRpdGxlID0gIk1BUEtfcFQyMDJZMjA0IHNpZ25hbGluZyBhY3Jvc3MgYWxsIGNlbGwgbGluZXMgaW4gTUNMUCBEYXRhc2V0IiwKICBjb2xvciA9ICJhY3Rpdml0eSIsIHBhbGV0dGUgPSBjKCJyZWQiLCAiZ3JleSIsICJncmVlbiIpLCAKICBsYWJlbCA9ICJTYW1wbGVfTmFtZSIsIGxhYmVsLnNlbGVjdCA9IGNlbGwubGluZXMuaW4ubWNscCwgcmVwZWwgPSBUUlVFLCB4bGFiID0gIkNlbGwgTGluZXMiLAogIGFkZCA9ICJzZWdtZW50cyIsIGxhYmVsLnJlY3RhbmdsZSA9IFRSVUUsIHlsYWIgPSAiTUFQS19wVDIwMlkyMDQgU2lnbmFsaW5nIiwKICBhZGQucGFyYW1zID0gbGlzdChjb2xvciA9ICJhY3Rpdml0eSIsIHBhbGV0dGUgPSBjKCJyZWQiLCAiZ3JleSIsICJncmVlbiIpKSkgKyAKICAgIHRoZW1lKGF4aXMudGV4dC54ID0gZWxlbWVudF9ibGFuaygpLCBheGlzLnRpY2tzID0gZWxlbWVudF9ibGFuaygpKQpgYGAKCjxkaXYgY2xhc3M9ImJsdWUtYm94Ij4KT25lIG9mIHRoZSAqKmFjdGl2YXRpbmcgcGhvc3Bob3J5bGF0aW9uIHNpdGVzIG9mIGBFUktgKiogaXMgdGhlICoqRVJLMSBUMjAyL1kyMDQqKi4gCkZyb20gdGhlIGFib3ZlIGZpZ3VyZSB3ZSBzZWUgYSB3ZWFrIHNpZ25hbGluZyBvZiB0aGlzIHBob3Nob3NpdGUgZm9yIHRoZSBgQTQ5OGAgY2VsbCBsaW5lIHdoaWNoIGRvZXMgbm90IGZ1bGx5IGNvcnJlbGF0ZSB3ZWxsIHdpdGggdGhlIHJlc3VsdHMgZnJvbSBvdXIgYW5hbHlzaXMgYWJvdmUgKGBFUktfZmAgb3ZlcmV4cHJlc3Npb24pLiBUaG91Z2gsIGluIGEgW2xhdGVyIHNlY3Rpb25dKCNhay1wZC1pbi1vdGhlci1jZWxsLWxpbmVzKSB3ZSBzaG93IHRoYXQgYEVSS19mYCBpcyBmb3VuZCBhY3RpdmUgaW4gYWxsIG1vZGVscyBmcm9tIGFsbCBjZWxsIGxpbmVzIHRoYXQgcHJlZGljdCB0aGUgYEFLLVBEYCBzeW5lcmd5IChmb3IgZXhhbXBsZSBldmVuIGluIGBVQUNDNjJgIGNlbGwgbGluZSB3aGljaCBoYXMgYSBoaWdoIHBob3Nob3J5bGF0aW9uIHNpZ25hbCBpbiB0aGUgYWJvdmUgZmlndXJlKS4KPC9kaXY+CjwvYnI+CgpVc2luZyB0aGUgbWF0cml4IGZyb20gVGFibGUgMiwgd2UgY291bnQgcGVyIG5ldHdvcmsgbm9kZSAqKnRoZSBudW1iZXIgb2YgdGltZXMgdGhhdCB0aGUgbm9kZSdzIGF2ZXJhZ2UgbGluayBvcGVyYXRvciBkaWZmZXJlbmNlIHZhbHVlIGhhcyBzdXJwYXNzZWQgYSBzcGVjaWZpZWQgdGhyZXNob2xkKiogLSBpLmUuIHRoZSBudW1iZXIgb2YgdGltZXMgaXQgaGFzIGJlZW4gZm91bmQgYXMgaW1wb3J0YW50IChhIGJpb21hcmtlcikgYWNyb3NzIGFsbCB0aGUgc3luZXJneSBzZXQgY29tcGFyaXNvbnMgKHNvIHRoZSBtb3JlIHRoZSBiZXR0ZXIpOgoKYGBge3IgTGluayBPcGVyYXRvciBCaW9tYXJrZXIgY29udGluZ2VuY3kgdGFibGUgYWNyb3NzIGFsbCBjb21wYXJpc29uIHNldHMgKEFLLVBLIC0gQ2VsbC1zcGVjaWZpYyAtIEE0OTgpLCByZXN1bHRzPSdhc2lzJ30KYmlvbWFya2VyLmxpbmsubWF0ID0gYmluYXJpemVfdG9fdGhyZXMobWF0ID0gZGlmZi5saW5rLm1hdCwgdGhyZXMgPSAwLjgpCgpiaW9tYXJrZXIubGluay5jb3VudHMgPSBjb2xTdW1zKGJpb21hcmtlci5saW5rLm1hdCkKcHJldHR5X3ByaW50X3ZlY3Rvcl9uYW1lc19hbmRfdmFsdWVzKHRhYmxlKGJpb21hcmtlci5saW5rLmNvdW50cykpCmBgYAoKVGhlIGFib3ZlIG1lYW5zIHRoYXQgdGhlcmUgd2VyZSAkNDQkIG5vZGVzIHRoYXQgd2VyZSAqemVybyogdGltZXMgZm91bmQgYXMgbGluayBvcGVyYXRvciBiaW9tYXJrZXJzIGFjcm9zcyBhbGwgc3luZXJneSBzZXQgY29tcGFyaXNvbnMsIG9uZSB0aGF0IHdhcyBmb3VuZCBvbmNlLCBvbmUgdGhhdCB3YXMgZm91bmQgdHdpY2UsICoqJDUkIHRoYXQgd2VyZSBmb3VuZCA2IHRpbWVzIGFuZCAkMSQgdGhhdCB3YXMgZm91bmQgMTEgdGltZXMqKi4gVGhlIGxhc3QgdHdvIGNhdGVnb3JpZXMgb2Ygbm9kZXMgYXJlOgoKYGBge3IgRXh0cmEgbGluayBvcGVyYXRvciBiaW9tYXJrZXJzIChDZWxsLXNwZWNpZmljIC0gQUstUEQpLCByZXN1bHRzPSdhc2lzJ30KcHJldHR5X3ByaW50X3ZlY3Rvcl9uYW1lcyhiaW9tYXJrZXIubGluay5jb3VudHNbYmlvbWFya2VyLmxpbmsuY291bnRzID09IDZdKQpwcmV0dHlfcHJpbnRfdmVjdG9yX25hbWVzKGJpb21hcmtlci5saW5rLmNvdW50c1tiaW9tYXJrZXIubGluay5jb3VudHMgPT0gMTFdKQpgYGAKCklmIHdlIHZpc3VhbGl6ZSB0aGUgKiphdmVyYWdlIGxpbmsgb3BlcmF0b3IgZGlmZmVyZW5jZSBhY3Jvc3MgYWxsIG9mIHRoZSBzeW5lcmd5IGNvbXBhcmlzb24gc2V0cyoqIGZyb20gVGFibGUgMiwgd2UgY2FuIHNlZSB0aGUgbm9kZXMgbWVudGlvbmVkIGFib3ZlOgoKYGBge3IgQXZlcmFnZSBsaW5rIG9wZXJhdG9yIGRpZmYgYWNyb3NzIGFsbCBzeW5lcmd5IHN1YnNldHMgKEFLLVBEKSwgY2FjaGU9VFJVRX0KcGxvdF9hdmdfbGlua19vcGVyYXRvcl9kaWZmX2dyYXBoKG5ldCwgZGlmZiA9IGNvbE1lYW5zKGRpZmYubGluay5tYXQpLCBsYXlvdXQgPSBuaWNlLmxheW91dCwgCiAgdGl0bGUgPSAiQXZlcmFnZSBsaW5rIG9wZXJhdG9yIGRpZmYgYWNyb3NzIGFsbCBzeW5lcmd5IHN1YnNldHMgKEFLLVBEKSIpCmBgYAoKPGRpdiBjbGFzcz0ib3JhbmdlLWJveCI+ClNvbWUgbm90ZXMvb2JzZXJ2YXRpb25zIG9uIHRoZSBzdHJ1Y3R1cmUgb2YgdGhlIGJvb2xlYW4gZXF1YXRpb25zIGZvdW5kIGFib3ZlOgo8L2Rpdj4KPC9icj4KClRoZSBib29sZWFuIGVxdWF0aW9uIG9mIHRoZSBgU1JDYCBub2RlIGlzOiBgU1JDICo9ICAoICBSVFBLX2YgKSBBTkQvT1IgTk9UICAoIENTSyApYCBhbmQgaXQncyBtZWFuIGxpbmsgb3BlcmF0b3IgZGlmZmVyZW5jZSB2YWx1ZSBhY3Jvc3MgYWxsIHN5bmVyZ3kgc2V0IGNvbXBhcmlzb25zIGlzIGByIG1lYW4oZGlmZi5saW5rLm1hdFssICJTUkMiXSlgIHdoaWNoIG1lYW5zIHRoYXQgb24gYXZlcmFnZSB0aGUgbG9naWMgb3BlcmF0b3IgdGhhdCBiaW5kcyBpdHMgdHdvIHJlZ3VsYXRvcnMgaXMgdGhlIGBBTkQgTk9UYCBhbmQgdGh1cyBpdCdzIG1vcmUgZGlmZmljdWx0IHRvIGhhdmUgaXQgYXMgYWN0aXZhdGVkICgxIGNhc2Ugb3V0IG9mIDQgaW4gYm9vbGVhbiBsb2dpYykuICoqVGhpcyBjb3JyZWxhdGVzIHdpdGggdGhlIGZhY3QgdGhhdCBpdCB3YXMgZm91bmQgYXMgbW9zdGx5IGluaGliaXRlZCBpbiB0aGUgYW5hbHlzaXMgYWJvdmUqKi4gQWxzbyBub3RlIHRoZSBlcXVhdGlvbnM6IAoKLSBgQ1NLICo9ICAoICBQUktBQ0EgKWAKLSBgUFJLQUNBICo9ICAoICAoICBORktCX2YgKSAgb3IgRk9TIClgCi0gYEZPUyAqPSAgKCAgKCAgKCAgRVJLX2YgKSAgb3IgUlNLX2YgKSAgb3IgU1JGIClgCgpTbywgd2hlbiBgRVJLX2ZgIGlzIG92ZXJleHByZXNzZWQsIGBDU0tgIGJlY29tZXMgYWN0aXZlLCB3aGljaCBtZWFucyB0aGF0IHRoZSBwcmV2YWxlbmNlIG9mIHRoZSBgQU5EIE5PVGAgbGluayBvcGVyYXRvciBtYWtlcyB0aGUgYWN0aXZpdHkgb2YgdGhlIGBTUkNgIG5vZGUgZGVwZW5kZW50IG9ubHkgb24gaXQncyBpbmhpYml0b3IgYENTS2AgKHNpbmNlIGl0J3MgYWN0aXZlKSBhbmQgbm90IG9uIHRoZSBhY3Rpdml0eSBvZiBgUlRQS19mYCBub2RlLgoKLSBgbVRPUkMxX2MgKj0gICggICggIFJIRUIgKSAgb3IgUlNLX2YgKSBBTkQvT1IgTk9UICAoIEFLVDFTMSApYAotIGBBS1QxUzEgKj0gIG5vdCAgKCBBS1RfZiApYAoKYG1UT1JDMV9jYCdzIG1lYW4gbGluayBvcGVyYXRvciBkaWZmZXJlbmNlIHZhbHVlIGFjcm9zcyBhbGwgc3luZXJneSBzZXQgY29tcGFyaXNvbnMgaXMgYHIgbWVhbihkaWZmLmxpbmsubWF0WywgIm1UT1JDMV9jIl0pYCB3aGljaCBtZWFucyB0aGF0IG9uIGF2ZXJhZ2UgdGhlIGxvZ2ljIG9wZXJhdG9yIHRoYXQgYmluZHMgaXRzIHR3byByZWd1bGF0b3JzIGlzIHRoZSBgT1IgTk9UYC4gCioqVGhpcyBnaXZlcyB0aGUgbm9kZSB0aGUgc3RydWN0dXJhbCBmbGV4aWJpbGl0eSB0byBiZWNvbWUgYWN0aXZlIHdoZW4gdGhlIGBBS2AgZHJ1ZyBpcyB1c2VkKio6IGBBS2AgaW5oaWJpdHMgYEFLVF9mYCBub2RlLCBtYWtpbmcgdGh1cyBgQUtUMVMxYCBhY3RpdmUsIHdoaWNoIGluIHR1cm4gbWFrZXMgdGhlIGBtVE9SQzFfY2AgZXF1YXRpb24gbGlrZSB0aGlzOiBgbVRPUkMxX2MgKj0gICggICggIFJIRUIgKSAgb3IgUlNLX2YgKSBBTkQvT1IgMGAuIFNvLCBhbiBgQU5EYCBsaW5rIG9wZXJhdG9yIHdvdWxkIHJlc3VsdCBhbHdheXMgaW4gYW4gaW5oaWJpdGVkIGBtVE9SQzFfY2Agd2hlcmVhcyBhbiBgT1JgIGxpbmsgb3BlcmF0b3Igd291bGQgZ2l2ZSB0aGUgcG9zc2liaWxpdHkgZm9yIHRoZSBgbVRPUkMxX2NgIHRvIGJlIGFjdGl2ZSBpbiBjYXNlIG9uZSBvZiBpdHMgYWN0aXZhdG9ycyBhcmUgYWN0aXZlLgoKCmBgYHtyIEluZHVjZWQgZ3JhcGhzIGludmVzdGlnYXRpb24sIGV2YWw9RkFMU0UsIGluY2x1ZGU9RkFMU0V9Cm5vZGVzID0gYygibVRPUkMxX2MiLCAiVFNDX2YiLCAiU1JDIiwgIkFLVF9mIiwgIk1FS19mIikKc3ViLm5ldCA9IGZpbHRlcl9uZXR3b3JrKG5ldCwgbm9kZXMsIGxldmVsID0gMSkKc3ViLm5vZGVzID0gVihzdWIubmV0KSRuYW1lCnNldC5zZWVkKDApCnN1Yi5sYXlvdXQgPSBsYXlvdXRfbmljZWx5KGdyYXBoID0gc3ViLm5ldCkKCmRpZmYgPSBjb2xNZWFucyhkaWZmLmxpbmsubWF0KQoKcGxvdF9hdmdfbGlua19vcGVyYXRvcl9kaWZmX2dyYXBoKHN1Yi5uZXQsIGRpZmYgPSBkaWZmW3N1Yi5ub2Rlc11bIWlzLm5hKGRpZmZbc3ViLm5vZGVzXSldLCAKICBsYXlvdXQgPSBzdWIubGF5b3V0LCB0aXRsZSA9ICJBdmVyYWdlIGxpbmsgb3BlcmF0b3IgZGlmZiBhY3Jvc3MgYWxsIHN5bmVyZ3kgc3Vic2V0cyAoQUstUEQpIikKYGBgCgo8ZGl2IGNsYXNzPSJibHVlLWJveCI+CldlIGNvbmNsdWRlIHRoYXQgY2FuY2VyIG1vZGVscyBpbiB3aGljaCB0aGUgYEFLLVBEYCBkcnVnIGNvbWJpbmF0aW9uIGlzIHN5bmVyZ2lzdGljLCB0ZW5kIHRvIGFsc28gaGF2ZSB0aGUgbm9kZXMgYFNSQ2AgYW5kIGBQVFBONmAgaW4gYW4gKippbmhpYml0ZWQgc3RhdGUqKiBhbmQgdGhlIGBTUkNgIG5vZGUgZGVwZW5kcyBvbiBib3RoIGl0J3MgcmVndWxhdG9ycywgYFJUUEtfZmAgYW5kIGBDU0tgLiAKVGhlIGxpbmsgb3BlcmF0b3JzIG9mIHRoZSBgbVRPUkMxX2NgIGFuZCBgVFNDX2ZgIGVxdWF0aW9ucyBhbHNvIHNlZW0gdG8gaGF2ZSBhbiBpbXBvcnRhbnQgcm9sZSBpbiB0aGUgbWFuaWZlc3RhdGlvbiBvZiB0aGlzIHN5bmVyZ3kuCjwvZGl2Pgo8L2JyPgoKIyMjIFJhbmRvbSB7LX0KCldlIGdldCB0aGUgYXZlcmFnZSBzdGF0ZSBhbmQgbGluayBvcGVyYXRvciBkaWZmZXJlbmNlcyBwZXIgbmV0d29yayBub2RlIGZvciB0aGUgYEE0OThgIGNlbGwgbGluZSBmcm9tIHRoZSByYW5kb20gbW9kZWxzOgoKYGBge3IgQUstUEQgZGlmZiAoUmFuZG9tIG1vZGVscyAtIEE0OTgpfQpBSy5QRC5hdmcuc3RhdGUuZGlmZi5yYW5kb20gPSByYW5kb20uc3luZXJneS5hbmFseXNpcy5yZXMkQTQ5OCRkaWZmLnN0YXRlLnN5bmVyZ2llcy5tYXRbIkFLLVBEIiwgXQpBSy5QRC5hdmcubGluay5kaWZmLnJhbmRvbSAgPSByYW5kb20uc3luZXJneS5hbmFseXNpcy5yZXMkQTQ5OCRkaWZmLmxpbmsuc3luZXJnaWVzLm1hdFsiQUstUEQiLCBdCmBgYAoKIyMjIyBBY3Rpdml0eSBzdGF0ZSBiaW9tYXJrZXJzIHstfQoKV2Ugd2lsbCBub3cgdmlzdWFsaXplIHRoZSBub2RlcyBhdmVyYWdlIHN0YXRlIGRpZmZlcmVuY2VzIGluIGEgbmV0d29yayBncmFwaC4gTm90ZSB0aGF0IHRoZSAqKmdvb2QgbW9kZWxzKiogYXJlIHRob3NlIHRoYXQgcHJlZGljdGVkIHRoZSBgQUstUERgIGRydWcgY29tYmluYXRpb24gdG8gYmUgKnN5bmVyZ2lzdGljKiBhbmQgd2VyZSBjb250cmFzdGVkIHRvIHRob3NlIHRoYXQgcHJlZGljdGVkIGl0IHRvIGJlICphbnRhZ29uaXN0aWMqICgqKmJhZCBtb2RlbHMqKikuIFRoZSBudW1iZXIgb2YgbW9kZWxzIGluIGVhY2ggY2F0ZWdvcnkgd2VyZToKCmBgYHtyIENvdW50IGdvb2QgdnMgYmFkIG1vZGVscyBmb3IgQUstUEQgc3luZXJneSAocmFuZG9tIG1vZGVscyksIHJlc3VsdHM9J2FzaXMnfQpkcnVnLmNvbWIgPSAiQUstUEQiCgpnb29kLm1vZGVscy5udW0gPSBzdW0ocmFuZG9tLm1vZGVsLnByZWRpY3Rpb25zWywgZHJ1Zy5jb21iXSA9PSAxICYgIWlzLm5hKHJhbmRvbS5tb2RlbC5wcmVkaWN0aW9uc1ssIGRydWcuY29tYl0pKQojIHVuaXF1ZSBnb29kIG1vZGVscwojIG5yb3codW5pcXVlKHJhbmRvbS5tb2RlbHMubGluay5vcGVyYXRvcltyYW5kb20ubW9kZWwucHJlZGljdGlvbnNbLCBkcnVnLmNvbWJdID09IDEgJiAhaXMubmEocmFuZG9tLm1vZGVsLnByZWRpY3Rpb25zWywgZHJ1Zy5jb21iXSksXSkpCmJhZC5tb2RlbHMubnVtICA9IHN1bShyYW5kb20ubW9kZWwucHJlZGljdGlvbnNbLCBkcnVnLmNvbWJdID09IDAgJiAhaXMubmEocmFuZG9tLm1vZGVsLnByZWRpY3Rpb25zWywgZHJ1Zy5jb21iXSkpCgpwcmV0dHlfcHJpbnRfc3RyaW5nKHBhc3RlMCgiTnVtYmVyIG9mICdnb29kJyByYW5kb20gbW9kZWxzIChBSy1QRCBzeW5lcmdpc3RpYykgaW4gdGhlIEE0OTggY2VsbCBsaW5lOiAiLCBnb29kLm1vZGVscy5udW0pKQpwcmV0dHlfcHJpbnRfc3RyaW5nKHBhc3RlMCgiTnVtYmVyIG9mICdiYWQnIHJhbmRvbSBtb2RlbHMgKEFLLVBEIGFudGFnb25pc3RpYykgaW4gdGhlIEE0OTggY2VsbCBsaW5lOiAiLCBiYWQubW9kZWxzLm51bSkpCmBgYAoKYGBge3IgQUstUEQgYWN0aXZpdHkgc3RhdGUgYmlvbWFya2VycyAoUmFuZG9tIG1vZGVscyAtIEE0OTgpLCBjYWNoZSA9IFRSVUV9CnBsb3RfYXZnX3N0YXRlX2RpZmZfZ3JhcGgobmV0LCBkaWZmID0gQUsuUEQuYXZnLnN0YXRlLmRpZmYucmFuZG9tLCAKICBsYXlvdXQgPSBuaWNlLmxheW91dCwgdGl0bGUgPSAiQUstUEQgYWN0aXZpdHkgc3RhdGUgYmlvbWFya2VycyAoUmFuZG9tIG1vZGVscyAtIEE0OTgpIikKYGBgCgpUaHVzLCB3ZSBjYW4gaWRlbnRpZnkgdGhlIGFjdGl2ZSBzdGF0ZSBiaW9tYXJrZXJzIChub3RlIHRoYXQgbm8gaW5oaWJpdGVkIGJpb21hcmtlcnMgYXQgdGhlIHNwZWNpZmllZCB0aHJlc2hvbGQgZGlmZmVyZW5jZSBsZXZlbCB3ZXJlIGZvdW5kKToKCmBgYHtyIEFLLVBEIGFjdGl2ZSBzdGF0ZSBiaW9tYXJrZXJzIChSYW5kb20gbW9kZWxzIC0gQTQ5OCksIHJlc3VsdHMgPSAnYXNpcyd9CkFLLlBELmFjdGl2ZS5iaW9tYXJrZXJzID0gQUsuUEQuYXZnLnN0YXRlLmRpZmYucmFuZG9tW0FLLlBELmF2Zy5zdGF0ZS5kaWZmLnJhbmRvbSA+IDAuOF0KcHJldHR5X3ByaW50X3ZlY3Rvcl9uYW1lcyhBSy5QRC5hY3RpdmUuYmlvbWFya2VycykKYGBgCgpgYGB7ciBBSy1QRCBpbmhpYml0ZWQgc3RhdGUgYmlvbWFya2VycyAoUmFuZG9tIG1vZGVscyAtIEE0OTgpLCByZXN1bHRzID0gJ2FzaXMnLCBpbmNsdWRlPUZBTFNFfQpBSy5QRC5pbmhpYml0ZWQuYmlvbWFya2VycyA9IEFLLlBELmF2Zy5zdGF0ZS5kaWZmLnJhbmRvbVtBSy5QRC5hdmcuc3RhdGUuZGlmZi5yYW5kb20gPCAtMC44XQpwcmV0dHlfcHJpbnRfdmVjdG9yX25hbWVzKEFLLlBELmluaGliaXRlZC5iaW9tYXJrZXJzKQpgYGAKCjxkaXYgY2xhc3M9ImJsdWUtYm94Ij4KVGhlIHJhbmRvbSBtb2RlbHMgdGhhdCBwcmVkaWN0ZWQgdGhlIGBBSy1QRGAgc3luZXJneSBhbHNvIHNob3cgYW4gb3ZlcmV4cHJlc3Npb24gb2YgdGhlIGBFUktfZmAgbm9kZS4KPC9kaXY+CjwvYnI+CgojIyMjIExpbmsgb3BlcmF0b3IgYmlvbWFya2VycyB7LX0KCldlIHZpc3VhbGl6ZSB0aGUgbm9kZXMgYXZlcmFnZSBsaW5rIG9wZXJhdG9yIGRpZmZlcmVuY2VzIGluIGEgbmV0d29yayBncmFwaDoKCmBgYHtyIEFLLVBEIGxpbmsgb3BlcmF0b3IgYmlvbWFya2VycyAoUmFuZG9tIG1vZGVscyAtIEE0OTgpLCBjYWNoZSA9IFRSVUV9CnBsb3RfYXZnX2xpbmtfb3BlcmF0b3JfZGlmZl9ncmFwaChuZXQsIGRpZmYgPSBBSy5QRC5hdmcubGluay5kaWZmLnJhbmRvbSwgCiAgbGF5b3V0ID0gbmljZS5sYXlvdXQsIHRpdGxlID0gIkFLLVBEIGxpbmsgb3BlcmF0b3IgYmlvbWFya2VycyAoUmFuZG9tIG1vZGVscyAtIEE0OTgpIikKYGBgCgo8ZGl2IGNsYXNzPSdibHVlLWJveCc+ClRoZSBpbXBvcnRhbmNlIG9mIHRoZSAqKk9SIE5PVCoqIGxpbmsgb3BlcmF0b3IgaW4gdGhlIGJvb2xlYW4gZXF1YXRpb24gb2YgYEVSS19mYCBpcyBhZ2FpbiBwcm92ZW4gdG8gYmUgY3J1Y2lhbCBmb3IgdGhlIG1hbmlmZXN0YXRpb24gb2YgdGhlIGBBSy1QRGAgc3luZXJneSwgYWxvbmcgd2l0aCB0aGUgbGluayBvcGVyYXRvcnMgb2YgdGhlIGVxdWF0aW9ucyBvZiB0aGUgYE1FS19mYCwgYFBURU5gIGFuZCBgUERQSzFgIG5vZGVzLgo8L2Rpdj4KPC9icj4KCiMjIyMgU3luZXJneSBzdWJzZXRzIGFuYWx5c2lzIHstfQoKV2UgcGVyZm9ybSB0aGUgc2FtZSBraW5kIG9mIGFuYWx5c2lzIGFzIHdpdGggdGhlIGNlbGwtc3BlY2lmaWMgbW9kZWxzOiBtb2RlbHMgdGhhdCBwcmVkaWN0IGEgc2V0IG9mIHN5bmVyZ2llcyB3aWxsIGJlIGNvbnRyYXN0ZWQgdG8gbW9kZWxzIHRoYXQgcHJlZGljdGVkIHRoZSBzYW1lIHNldCB3aXRoIHRoZSBhZGRpdGlvbiBvZiB0aGUgZXh0cmEgYEFLLVBEYCBzeW5lcmd5LCBhbGxvd2luZyB1cyB0aHVzIHRvIHJlZmluZSB0aGUgYWN0aXZpdHkgc3RhdGUgYW5kIGxpbmsgb3BlcmF0b3IgYmlvbWFya2VycyB3ZSBmb3VuZCBhYm92ZSBmcm9tIHRoZSByYW5kb20gbW9kZWxzLgoKV2UgZmlyc3QgY29uc3RydWN0IHR3byBtYXRyaWNlczogaW4gdGhlIGZpcnN0LCAqKmVhY2ggcm93IGlzIGEgc2V0IHZzIHN1YnNldCBhdmVyYWdlIGFjdGl2aXR5IGRpZmZlcmVuY2UgdmVjdG9yIG9mIHRoZSBuZXR3b3JrIG5vZGVzKiosIHdoaWxlIG9uIHRoZSBzZWNvbmQgKiplYWNoIHJvdyBpcyBhIHNldCB2cyBzdWJzZXQgYXZlcmFnZSBsaW5rIG9wZXJhdG9yIGRpZmZlcmVuY2UgdmVjdG9yIG9mIHRoZSBuZXR3b3JrIG5vZGVzKio6CmBgYHtyIFN5bmVyZ3kgU3Vic2V0cyBDb21wYXJpc29uIChSYW5kb20gLSBBNDk4KSwgY2FjaGU9VFJVRX0KcmVzID0gZ2V0X3N5bmVyZ3lfY29tcGFyaXNvbl9zZXRzKHJhbmRvbS5zeW5lcmd5LmFuYWx5c2lzLnJlcyRBNDk4JHN5bmVyZ3kuc3Vic2V0LnN0YXRzKQpBSy5QRC5yZXMgPSByZXMgJT4lIGZpbHRlcihzeW5lcmdpZXMgPT0gIkFLLVBEIikKCmRpZmYuc3RhdGUubGlzdC5yYW5kb20gPSBsaXN0KCkKZGlmZi5saW5rLmxpc3QucmFuZG9tID0gbGlzdCgpCmZvciAoaSBpbiAxOm5yb3coQUsuUEQucmVzKSkgewogIHN5bmVyZ3kuc2V0ICAgID0gQUsuUEQucmVzW2ksIDJdCiAgc3luZXJneS5zdWJzZXQgPSBBSy5QRC5yZXNbaSwgM10KICAKICBzeW5lcmd5LnNldC5zdHIgICAgPSB1bmxpc3Qoc3Ryc3BsaXQoeCA9IHN5bmVyZ3kuc2V0LCBzcGxpdCA9ICIsIikpCiAgc3luZXJneS5zdWJzZXQuc3RyID0gdW5saXN0KHN0cnNwbGl0KHggPSBzeW5lcmd5LnN1YnNldCwgc3BsaXQgPSAiLCIpKQogIAogICMgY291bnQgbW9kZWxzCiAgc3luZXJneS5zZXQubW9kZWxzLm51bSA9IGNvdW50X21vZGVsc190aGF0X3ByZWRpY3Rfc3luZXJnaWVzKHN5bmVyZ3kuc2V0LnN0ciwgcmFuZG9tLm1vZGVsLnByZWRpY3Rpb25zKQogIHN5bmVyZ3kuc3Vic2V0Lm1vZGVscy5udW0gPSBjb3VudF9tb2RlbHNfdGhhdF9wcmVkaWN0X3N5bmVyZ2llcyhzeW5lcmd5LnN1YnNldC5zdHIsIHJhbmRvbS5tb2RlbC5wcmVkaWN0aW9ucykKICAjIHByaW50KHBhc3RlMChzeW5lcmd5LnNldC5tb2RlbHMubnVtLCAiICIsIHN5bmVyZ3kuc3Vic2V0Lm1vZGVscy5udW0pKQogIAogICMgaWYgdG9vIHNtYWxsIG51bWJlciBvZiBtb2RlbHMsIHNraXAgdGhlIGRpZmYgdmVjdG9yCiAgaWYgKChzeW5lcmd5LnNldC5tb2RlbHMubnVtIDw9IDMpIHwgKHN5bmVyZ3kuc2V0Lm1vZGVscy5udW0gPD0gMykpIAogICAgbmV4dAogIAogICMgZ2V0IHRoZSBkaWZmCiAgZGlmZi5zdGF0ZS5hay5wZCA9IGdldF9hdmdfYWN0aXZpdHlfZGlmZl9iYXNlZF9vbl9zeW5lcmd5X3NldF9jbXAoc3luZXJneS5zZXQuc3RyLCBzeW5lcmd5LnN1YnNldC5zdHIsIHJhbmRvbS5tb2RlbC5wcmVkaWN0aW9ucywgcmFuZG9tLm1vZGVscy5zdGFibGUuc3RhdGUpCiAgZGlmZi5saW5rLmFrLnBkID0gZ2V0X2F2Z19saW5rX29wZXJhdG9yX2RpZmZfYmFzZWRfb25fc3luZXJneV9zZXRfY21wKHN5bmVyZ3kuc2V0LnN0ciwgc3luZXJneS5zdWJzZXQuc3RyLCByYW5kb20ubW9kZWwucHJlZGljdGlvbnMsIHJhbmRvbS5tb2RlbHMubGluay5vcGVyYXRvcikKICAKICBkaWZmLnN0YXRlLmxpc3QucmFuZG9tW1twYXN0ZTAoc3luZXJneS5zZXQsICIgdnMgIiwgc3luZXJneS5zdWJzZXQpXV0gPSBkaWZmLnN0YXRlLmFrLnBkCiAgZGlmZi5saW5rLmxpc3QucmFuZG9tW1twYXN0ZTAoc3luZXJneS5zZXQsICIgdnMgIiwgc3luZXJneS5zdWJzZXQpXV0gPSBkaWZmLmxpbmsuYWsucGQKfQoKZGlmZi5zdGF0ZS5tYXQucmFuZG9tID0gZG8uY2FsbChyYmluZCwgZGlmZi5zdGF0ZS5saXN0LnJhbmRvbSkKZGlmZi5saW5rLm1hdC5yYW5kb20gPSBkby5jYWxsKHJiaW5kLCBkaWZmLmxpbmsubGlzdC5yYW5kb20pCgpjYXB0aW9uLnRpdGxlLjMgPSAiVGFibGUgMzogQXZlcmFnZSBhY3Rpdml0eSBkaWZmZXJlbmNlIHZhbHVlcyBhY3Jvc3MgYWxsIHN5bmVyZ3kgc2V0IGNvbXBhcmlzb25zIChBSy1QRCkiCmRhdGF0YWJsZShkYXRhID0gZGlmZi5zdGF0ZS5tYXQucmFuZG9tWywgYygiU1JDIiwgIkNTSyIsICJNRUtfZiIsICJTVEFUMSIsICJQVFBONiIpXSwgb3B0aW9ucyA9IGxpc3QoCiAgICBzZWFyY2hpbmcgPSBGQUxTRSwgcGFnZUxlbmd0aCA9IDUsIGxlbmd0aE1lbnUgPSBjKDUsIDEwKSksCiAgICBjYXB0aW9uID0gaHRtbHRvb2xzOjp0YWdzJGNhcHRpb24oY2FwdGlvbi50aXRsZS4zLCBzdHlsZT0iY29sb3I6I2RkNDgxNDsgZm9udC1zaXplOiAxOHB4IikpICU+JSAKICAgICAgZm9ybWF0Um91bmQoMTpuY29sKGRpZmYuc3RhdGUubWF0LnJhbmRvbSksIGRpZ2l0cyA9IDMpCgpjYXB0aW9uLnRpdGxlLjQgPSAiVGFibGUgNDogQXZlcmFnZSBsaW5rIG9wZXJhdG9yIGRpZmZlcmVuY2UgdmFsdWVzIGFjcm9zcyBhbGwgc3luZXJneSBzZXQgY29tcGFyaXNvbnMgKEFLLVBEKSIKZGF0YXRhYmxlKGRhdGEgPSBkaWZmLmxpbmsubWF0LnJhbmRvbVssIGMoIlNSQyIsICJSQUNfZiIsICJNRUtfZiIsICJTVEFUMSIsICJQVEVOIildLCBvcHRpb25zID0gbGlzdCgKICAgIHNlYXJjaGluZyA9IEZBTFNFLCBwYWdlTGVuZ3RoID0gNSwgbGVuZ3RoTWVudSA9IGMoNSwgMTApKSwKICAgIGNhcHRpb24gPSBodG1sdG9vbHM6OnRhZ3MkY2FwdGlvbihjYXB0aW9uLnRpdGxlLjQsIHN0eWxlPSJjb2xvcjojZGQ0ODE0OyBmb250LXNpemU6IDE4cHgiKSkgJT4lIAogICAgICBmb3JtYXRSb3VuZCgxOm5jb2woZGlmZi5saW5rLm1hdC5yYW5kb20pLCBkaWdpdHMgPSAzKQpgYGAKClVzaW5nIHRoZSBtYXRyaXggZnJvbSBUYWJsZSAzICh3aGVyZSB3ZSBzaG93IGp1c3QgJDUkIG5vZGVzKSwgd2UgY291bnQgcGVyIG5ldHdvcmsgbm9kZSAqKnRoZSBudW1iZXIgb2YgdGltZXMgdGhhdCB0aGUgbm9kZSdzIGF2ZXJhZ2UgYWN0aXZpdHkgZGlmZmVyZW5jZSB2YWx1ZSBoYXMgc3VycGFzc2VkIGEgc3BlY2lmaWVkIHRocmVzaG9sZCoqIC0gaS5lLiB0aGUgbnVtYmVyIG9mIHRpbWVzIGl0IGhhcyBiZWVuIGZvdW5kIGFzIGltcG9ydGFudCAoYSBiaW9tYXJrZXIpIGFjcm9zcyBhbGwgdGhlIHN5bmVyZ3kgc2V0IGNvbXBhcmlzb25zIChzbyB0aGUgbW9yZSB0aGUgYmV0dGVyKToKYGBge3IgQWN0aXZpdHkgU3RhdGUgQmlvbWFya2VyIGNvbnRpbmdlbmN5IHRhYmxlIGFjcm9zcyBhbGwgY29tcGFyaXNvbiBzZXRzIChBSy1QRCAtIFJhbmRvbSksIHJlc3VsdHM9J2FzaXMnfQpiaW9tYXJrZXIuc3RhdGUubWF0LnJhbmRvbSA9IGJpbmFyaXplX3RvX3RocmVzKG1hdCA9IGRpZmYuc3RhdGUubWF0LnJhbmRvbSwgdGhyZXMgPSAwLjcpCgpiaW9tYXJrZXIuc3RhdGUuY291bnRzLnJhbmRvbSA9IGNvbFN1bXMoYmlvbWFya2VyLnN0YXRlLm1hdC5yYW5kb20pCnByZXR0eV9wcmludF92ZWN0b3JfbmFtZXNfYW5kX3ZhbHVlcyh0YWJsZShiaW9tYXJrZXIuc3RhdGUuY291bnRzLnJhbmRvbSkpCmBgYAoKU28sIHRoZXJlIGFyZSAkMiQgbm9kZXMgdGhhdCB3ZXJlIGZvdW5kIGFzICoqYWN0aXZpdHkgc3RhdGUgYmlvbWFya2VycyoqICQ2JCB0aW1lcyBhY3Jvc3MgYWxsIHN5bmVyZ3kgc2V0IGNvbXBhcmlzb25zLiBUaGVzZSBub2RlcyBhcmU6CgpgYGB7ciBFeHRyYSBhY3Rpdml0eSBzdGF0ZSBiaW9tYXJrZXJzIChBSy1QRCAtIFJhbmRvbSksIHJlc3VsdHM9J2FzaXMnfQpwcmV0dHlfcHJpbnRfdmVjdG9yX25hbWVzKGJpb21hcmtlci5zdGF0ZS5jb3VudHMucmFuZG9tW2Jpb21hcmtlci5zdGF0ZS5jb3VudHMucmFuZG9tID09IDZdKQpgYGAKCkJ1dCB0aGUgdG90YWwgbnVtYmVyIG9mIGNvbXBhcmlzb25zIHdhcyBgciBucm93KGJpb21hcmtlci5zdGF0ZS5tYXQucmFuZG9tKWAgc28gd2UgY291bGQgYXJndWUgdGhhdCAqKnRoaXMgaXMgbm90IGEgc3RhdGlzdGljYWxseSBzaWduaWZpY2FudCByZXN1bHQqKiwgd2hpY2ggY2FuIGJlIHNlZW4gY2xlYXJseSBpbiB0aGUgbmV4dCBncmFwaCB3aGVyZSB3ZSB2aXN1YWxpemUgdGhlICoqYXZlcmFnZSBhY3Rpdml0eSBzdGF0ZSBkaWZmZXJlbmNlIGFjcm9zcyBhbGwgc3luZXJneSBjb21wYXJpc29uIHNldHMqKiBmcm9tIFRhYmxlIDM6CgpgYGB7ciBBdmVyYWdlIGFjdGl2aXR5IHN0YXRlIGRpZmYgYWNyb3NzIGFsbCBzeW5lcmd5IHN1YnNldHMgKEFLLVBELCByYW5kb20gbW9kZWxzKSwgY2FjaGU9VFJVRX0KcGxvdF9hdmdfc3RhdGVfZGlmZl9ncmFwaChuZXQsIGRpZmYgPSBjb2xNZWFucyhkaWZmLnN0YXRlLm1hdC5yYW5kb20pLCBsYXlvdXQgPSBuaWNlLmxheW91dCwgCiAgdGl0bGUgPSAiQXZlcmFnZSBhY3Rpdml0eSBzdGF0ZSBkaWZmIGFjcm9zcyBhbGwgc3luZXJneSBzdWJzZXRzIChBSy1QRCkiKQpgYGAKClVzaW5nIHRoZSBtYXRyaXggZnJvbSBUYWJsZSA0LCB3ZSBjb3VudCBwZXIgbmV0d29yayBub2RlICoqdGhlIG51bWJlciBvZiB0aW1lcyB0aGF0IHRoZSBub2RlJ3MgYXZlcmFnZSBsaW5rIG9wZXJhdG9yIGRpZmZlcmVuY2UgdmFsdWUgaGFzIHN1cnBhc3NlZCBhIHNwZWNpZmllZCB0aHJlc2hvbGQqKiAtIGkuZS4gdGhlIG51bWJlciBvZiB0aW1lcyBpdCBoYXMgYmVlbiBmb3VuZCBhcyBpbXBvcnRhbnQgKGEgYmlvbWFya2VyKSBhY3Jvc3MgYWxsIHRoZSBzeW5lcmd5IHNldCBjb21wYXJpc29ucyAoc28gdGhlIG1vcmUgdGhlIGJldHRlcik6CgpgYGB7ciBMaW5rIE9wZXJhdG9yIEJpb21hcmtlciBjb250aW5nZW5jeSB0YWJsZSBhY3Jvc3MgYWxsIGNvbXBhcmlzb24gc2V0cyAoQUstUEsgLSBSYW5kb20pLCByZXN1bHRzPSdhc2lzJ30KYmlvbWFya2VyLmxpbmsubWF0LnJhbmRvbSA9IGJpbmFyaXplX3RvX3RocmVzKG1hdCA9IGRpZmYubGluay5tYXQucmFuZG9tLCB0aHJlcyA9IDAuNykKCmJpb21hcmtlci5saW5rLmNvdW50cy5yYW5kb20gPSBjb2xTdW1zKGJpb21hcmtlci5saW5rLm1hdC5yYW5kb20pCnByZXR0eV9wcmludF92ZWN0b3JfbmFtZXNfYW5kX3ZhbHVlcyh0YWJsZShiaW9tYXJrZXIubGluay5jb3VudHMucmFuZG9tKSkKYGBgCgpTbywgdGhlcmUgd2FzIGEgbm9kZSB0aGF0IHdhcyBmb3VuZCAkNyQgdGltZXMgKG91dCBvZiBhIHRvdGFsIG9mIGByIG5yb3coYmlvbWFya2VyLmxpbmsubWF0LnJhbmRvbSlgLCAqKnNvIG5vdCBzdGF0aXN0aWNhbGx5IHNpZ25pZmljYW50KiopIGFzIGEgbGluayBvcGVyYXRvciBiaW9tYXJrZXJzIGFjcm9zcyBhbGwgc3luZXJneSBzZXQgY29tcGFyaXNvbnM6CiAKYGBge3IgRXh0cmEgbGluayBvcGVyYXRvciBiaW9tYXJrZXJzIChBSy1QRCAtIFJhbmRvbSksIHJlc3VsdHM9J2FzaXMnfQpwcmV0dHlfcHJpbnRfdmVjdG9yX25hbWVzKGJpb21hcmtlci5saW5rLmNvdW50cy5yYW5kb21bYmlvbWFya2VyLmxpbmsuY291bnRzLnJhbmRvbSA9PSA3XSkKYGBgCgpXZSB2aXN1YWxpemUgdGhlICoqYXZlcmFnZSBsaW5rIG9wZXJhdG9yIGRpZmZlcmVuY2UgYWNyb3NzIGFsbCBzeW5lcmd5IGNvbXBhcmlzb24gc2V0cyoqIGZyb20gVGFibGUgNDoKYGBge3IgQXZlcmFnZSBsaW5rIG9wZXJhdG9yIGRpZmYgYWNyb3NzIGFsbCBzeW5lcmd5IHN1YnNldHMgKEFLLVBELCByYW5kb20pLCBjYWNoZT1UUlVFfQpwbG90X2F2Z19saW5rX29wZXJhdG9yX2RpZmZfZ3JhcGgobmV0LCBkaWZmID0gY29sTWVhbnMoZGlmZi5saW5rLm1hdC5yYW5kb20pLCBsYXlvdXQgPSBuaWNlLmxheW91dCwgCiAgdGl0bGUgPSAiQXZlcmFnZSBsaW5rIG9wZXJhdG9yIGRpZmYgYWNyb3NzIGFsbCBzeW5lcmd5IHN1YnNldHMgKEFLLVBEKSIpCmBgYAoKIyMjIEFLLVBEIGluIG90aGVyIGNlbGwgbGluZXMgey19CgpUaG91Z2ggdGhlIGBBSy1QRGAgc3luZXJneSB3YXMgb2JzZXJ2ZWQgYW5kIHByZWRpY3RlZCBpbiB0aGUgQTQ5OCBtb2RlbCBkYXRhc2V0LCB3ZSBpbnZlc3RpZ2F0ZSBpdHMgYmlvbWFya2VycyBpbiB0aGUgb3RoZXIgY2VsbCBsaW5lcyB3aGVyZSAqKml0IHdhcyBwcmVkaWN0ZWQgYXMgYSBGYWxzZSBQb3NpdGl2ZSAoRlApIHN5bmVyZ3kgKHByZWRpY3RlZCBieSB0aGUgbW9kZWxzIGJ1dCBub3Qgb2JzZXJ2ZWQgaW4gdGhlIGV4cGVyaW1lbnRzKSoqLiAKVGh1cywgd2UgY2FuIHN0aWxsIHNlZSBpZiB0aGVyZSBhcmUgYW55IGNvbW1vbiAoYWN0aXZpdHkgc3RhdGUgYW5kIGxpbmsgb3BlcmF0b3IpIGJpb21hcmtlcnMgZm9yIGBBSy1QRGAgYWNyb3NzIHRoZSBhbGwgdGhlIGNlbGwtc3BlY2lmaWMgbW9kZWxzIGJ5IGNvbnRyYXN0aW5nIGluIGVhY2ggY2VsbCBsaW5lIHRoZSBtb2RlbHMgdGhhdCBwcmVkaWN0ZWQgYEFLLVBEYCB2cyB0aGUgbW9kZWxzIHRoYXQgZGlkIG5vdDoKCmBgYHtyIEFLLVBEIGF2ZXJhZ2UgYWN0aXZpdHkgc3RhdGUgZGlmZmVyZW5jZSBpbiBhbGwgY2VsbCBsaW5lcyBleGNlcHQgQTQ5OH0KZHJ1Zy5jb21iID0gIkFLLVBEIgoKYWsucGQuZGlmZi5zdGF0ZS5saXN0ID0gbGlzdCgpCmFrLnBkLmRpZmYubGluay5saXN0ID0gbGlzdCgpCgpmb3IgKGNlbGwubGluZSBpbiBjZWxsLmxpbmVzKSB7CiAgaWYgKGNlbGwubGluZSA9PSAiQTQ5OCIpIAogICAgbmV4dAogIAogIGFrLnBkLmRpZmYuc3RhdGUubGlzdFtbY2VsbC5saW5lXV0gPSBnZXRfYXZnX2FjdGl2aXR5X2RpZmZfYmFzZWRfb25fc3BlY2lmaWNfc3luZXJneV9wcmVkaWN0aW9uKAogICAgbW9kZWwucHJlZGljdGlvbnMgPSBtb2RlbC5wcmVkaWN0aW9ucy5wZXIuY2VsbC5saW5lW1tjZWxsLmxpbmVdXSwgCiAgICBtb2RlbHMuc3RhYmxlLnN0YXRlID0gbW9kZWxzLnN0YWJsZS5zdGF0ZS5wZXIuY2VsbC5saW5lW1tjZWxsLmxpbmVdXSwgCiAgICBkcnVnLmNvbWIpCiAgYWsucGQuZGlmZi5saW5rLmxpc3RbW2NlbGwubGluZV1dID0gZ2V0X2F2Z19saW5rX29wZXJhdG9yX2RpZmZfYmFzZWRfb25fc3BlY2lmaWNfc3luZXJneV9wcmVkaWN0aW9uKAogICAgbW9kZWwucHJlZGljdGlvbnMgPSBtb2RlbC5wcmVkaWN0aW9ucy5wZXIuY2VsbC5saW5lW1tjZWxsLmxpbmVdXSwgCiAgICBtb2RlbHMubGluay5vcGVyYXRvciA9IG1vZGVscy5saW5rLm9wZXJhdG9ycy5wZXIuY2VsbC5saW5lW1tjZWxsLmxpbmVdXSwgCiAgICBkcnVnLmNvbWIpCiAgIyBwcmludChjb3VudF9tb2RlbHNfdGhhdF9wcmVkaWN0X3N5bmVyZ2llcyhkcnVnLmNvbWIsIG1vZGVsLnByZWRpY3Rpb25zID0gbW9kZWwucHJlZGljdGlvbnMucGVyLmNlbGwubGluZVtbY2VsbC5saW5lXV0pKQp9Cgphay5wZC5kaWZmLnN0YXRlLm1hdCA9IGRvLmNhbGwocmJpbmQsIGFrLnBkLmRpZmYuc3RhdGUubGlzdCkKYWsucGQuZGlmZi5saW5rLm1hdCA9IGRvLmNhbGwocmJpbmQsIGFrLnBkLmRpZmYubGluay5saXN0KQpgYGAKCjxkaXYgY2xhc3M9ImJsdWUtYm94Ij4KYEVSS19mYCB3YXMgdGhlIG9uZSBub2RlIHRoYXQgd2FzIGZvdW5kIGFzIGFuICoqYWN0aXZpdHkgc3RhdGUgJiBsaW5rIG9wZXJhdG9yIGBBSy1QRGAgYmlvbWFya2VyKiogaW4gYWxsIGNlbGwgbGluZXMKPC9kaXY+CgpgYGB7ciBBY3Rpdml0eSBzdGF0ZSBhbmQgbGluayBvcGVyYXRvciBiaW9tYXJrZXJzIChBSy1QRCwgYWxsIGNlbGwgbGluZXMgZXhjZXB0IEE0OTgpLCByZXN1bHRzPSdhc2lzJ30KYWsucGQuYmlvbWFya2VyLnN0YXRlLm1hdCA9IGJpbmFyaXplX3RvX3RocmVzKG1hdCA9IGFrLnBkLmRpZmYuc3RhdGUubWF0LCB0aHJlcyA9IDAuNykKYWsucGQuYmlvbWFya2VyLmxpbmsubWF0ICA9IGJpbmFyaXplX3RvX3RocmVzKG1hdCA9IGFrLnBkLmRpZmYubGluay5tYXQsIHRocmVzID0gMC43KQoKYWsucGQuYmlvbWFya2VyLnN0YXRlLm1hdC5jb3VudHMgPSBjb2xTdW1zKGFrLnBkLmJpb21hcmtlci5zdGF0ZS5tYXQpCmFrLnBkLmJpb21hcmtlci5saW5rLm1hdC5jb3VudHMgPSBjb2xTdW1zKGFrLnBkLmJpb21hcmtlci5saW5rLm1hdCkKCiNwcmV0dHlfcHJpbnRfdmVjdG9yX25hbWVzX2FuZF92YWx1ZXModGFibGUoYWsucGQuYmlvbWFya2VyLnN0YXRlLm1hdC5jb3VudHMpKQojcHJldHR5X3ByaW50X3ZlY3Rvcl9uYW1lc19hbmRfdmFsdWVzKHRhYmxlKGFrLnBkLmJpb21hcmtlci5saW5rLm1hdC5jb3VudHMpKQoKcHJldHR5X3ByaW50X3ZlY3Rvcl9uYW1lcyhhay5wZC5iaW9tYXJrZXIuc3RhdGUubWF0LmNvdW50c1thay5wZC5iaW9tYXJrZXIuc3RhdGUubWF0LmNvdW50cyA9PSA3XSkKcHJldHR5X3ByaW50X3ZlY3Rvcl9uYW1lcyhhay5wZC5iaW9tYXJrZXIubGluay5tYXQuY291bnRzW2FrLnBkLmJpb21hcmtlci5saW5rLm1hdC5jb3VudHMgPT0gNl0pCmBgYAoKIyMgUiBzZXNzaW9uIGluZm8gey19CgpgYGB7ciBzZXNzaW9uIGluZm8sIGNvbW1lbnQ9IiJ9CnhmdW46OnNlc3Npb25faW5mbygpCmBgYAo=